De nombreux langages, afin de gérer les modèles asynchrones davantage comme des séquences normales, incluent une bibliothèque de solutions intéressantes, appelées promesses, différés ou futurs. Les promesses de JavaScript peuvent favoriser la séparation des préoccupations à la place d'interfaces étroitement couplées. Cet article parle des promesses JavaScript basées sur la norme Promises/A. [http://wiki.commonjs.org/wiki/Promises/A]
Cas d'utilisation de la promesse :
La promesse JavaScript est un objet qui promet de renvoyer une valeur dans le futur. Est un objet de données avec un comportement bien défini. Une promesse a trois états possibles :
Une promesse qui a été rejetée ou complétée est considérée comme résolue. Une promesse ne peut passer que du statut en attente au statut résolu. Après cela, l’état de la promesse reste inchangé. Une promesse peut exister longtemps après la fin du traitement correspondant. En d’autres termes, nous pouvons obtenir les résultats du traitement plusieurs fois. Nous obtenons le résultat en appelant promise.then(). Cette fonction ne reviendra que lorsque le traitement correspondant à la promesse sera terminé. Nous pouvons enchaîner de manière flexible un ensemble de promesses. Ces fonctions « alors » concaténées doivent renvoyer une nouvelle promesse ou la plus ancienne.
Avec ce style, nous pouvons écrire du code asynchrone tout comme nous écrivons du code synchrone. Principalement réalisé en combinant les promesses :
Pourquoi s'embêter ? Ne pouvons-nous pas simplement utiliser la fonction de rappel de base ?
Problème avec la fonction de rappel
Les fonctions de rappel sont adaptées aux événements simples et récurrents, comme la validation d'un formulaire basée sur un clic, ou la sauvegarde des résultats d'un appel REST. Les fonctions de rappel créent également une chaîne de code, avec une fonction de rappel appelant une fonction REST, configurant une nouvelle fonction de rappel pour la fonction REST, et cette nouvelle fonction de rappel appelant une autre fonction REST, et ainsi de suite. La croissance horizontale du code est supérieure à la croissance verticale. La fonction de rappel semble simple, jusqu'à ce que nous ayons besoin d'un résultat, et nous en avons besoin immédiatement, immédiatement pour l'utiliser dans le calcul de la ligne suivante.
'use strict'; var i = 0; function log(data) {console.log('%d %s', ++i, data); }; function validate() { log("Wait for it ..."); // Sequence of four Long-running async activities setTimeout(function () { log('result first'); setTimeout(function () { log('result second'); setTimeout(function () { log('result third'); setTimeout(function () { log('result fourth') }, 1000); }, 1000); }, 1000); }, 1000); }; validate();
J'utilise timeout pour simuler des opérations asynchrones. La méthode de gestion des exceptions est pénible et peut facilement exploiter le comportement en aval. Lorsque nous écrivons des rappels, l’organisation du code devient confuse. La figure 2 montre un flux de validation fictif qui peut être exécuté sur NodeJS REPL. Dans la section suivante, nous passerons du modèle pyramidal du malheur à une promesse continue.
Figure
'use strict'; var i = 0; function log(data) {console.log('%d %s', ++i, data); }; // Asynchronous fn executes a callback result fn function async(arg, callBack) { setTimeout(function(){ log('result ' + arg); callBack(); }, 1000); }; function validate() { log("Wait for it ..."); // Sequence of four Long-running async activities async('first', function () { async('second',function () { async('third', function () { async('fourth', function () {}); }); }); }); }; validate();
Résultats de l'exécution dans NodeJS REPL
$ node scripts/examp2b.js 1 Wait for it ... 2 result first 3 result second 4 result third 5 result fourth $
J'ai rencontré une fois une situation où la validation dynamique AngularJS limitait dynamiquement la valeur de l'élément de formulaire en fonction de la valeur de la table correspondante. La plage de valeurs valide de l'élément de limite est définie sur le service REST.
J'ai écrit un planificateur pour faire fonctionner la pile de fonctions en fonction de la valeur demandée afin d'éviter l'imbrication des rappels. Le planificateur extrait la fonction de la pile et l'exécute. Le rappel de la fonction appellera à nouveau le planificateur à la fin jusqu'à ce que la pile soit effacée. Chaque rappel enregistre toutes les erreurs de validation renvoyées par l'appel de validation à distance.
Je pense que ce que j'ai écrit est un anti-modèle. Si j'utilise la promesse fournie par l'appel $http d'Angular, ma réflexion tout au long du processus de vérification sera plus linéaire, comme une programmation synchrone. Les chaînes de promesses aplaties sont lisibles. Continuer...
Utiliser les promesses
La bibliothèque de promesses Kew est utilisée. Il en va de même pour la bibliothèque Q. Pour utiliser la bibliothèque, importez d'abord la bibliothèque kew dans NodeJS à l'aide de npm, puis chargez le code dans NodeJS REPL.
Figure
'use strict'; var Q = require('kew'); var i = 0; function log(data) {console.log('%d %s', ++i, data); }; // Asynchronous fn returns a promise function async(arg) { var deferred = Q.defer(); setTimeout(function () { deferred.resolve('result ' + arg);\ }, 1000); return deferred.promise; }; // Flattened promise chain function validate() { log("Wait for it ..."); async('first').then(function(resp){ log(resp); return async('second'); }) .then(function(resp){ log(resp); return async('third') }) .then(function(resp){ log(resp); return async('fourth'); }) .then(function(resp){ log(resp); }).fail(log); }; validate();
Le résultat est le même que lors de l'utilisation de rappels imbriqués :
$ node scripts/examp2-pflat.js 1 Wait for it ... 2 result first 3 result second 4 result third 5 result fourth $
该代码稍微“长高”了,但我认为更易于理解和修改。更易于加上适当的错误处理。在链的末尾调用fail用于捕获链中错误,但我也可以在任何一个then里面提供一个reject的处理函数做相应的处理。
服务器 或 浏览器
Promises在浏览器中就像在NodeJS服务器中一样有效。下面的地址, http://jsfiddle.net/mauget/DnQDx/,指向JSFiddle的一个展示如何使用一个promise的web页面。 JSFiddle所有的代码是可修改的。我故意操作随意动作。你可以试几次得到相反的结果。它是可以直接扩展到多个promise链, 就像前面NodeJS例子。
并行 Promises
考虑一个异步操作喂养另一个异步操作。让后者包括三个并行异步行为,反过来,喂最后一个行动。只有当所有平行的子请求通过才能通过。这是灵感来自偶遇一打MongoDB操作。有些是合格的并行操作。我实现了promises的流流程图。
我们怎么会模拟那些在该图中心行的并行promises?关键是,最大的promise库有一个全功能,它产生一个包含一组子promises的父promie。当所有的子promises通过,父promise通过。如果有一个子promise拒绝,父promise拒绝。
让十个并行的promises每个都包含一个文字promise。只有当十个子类通过或如果任何子类拒绝,最后的then方法才能完成。
Figure
var promiseVals = ['To ', 'be, ', 'or ', 'not ', 'to ', 'be, ', 'that ', 'is ', 'the ', 'question.']; var startParallelActions = function (){ var promises = []; // Make an asynchronous action from each literal promiseVals.forEach(function(value){ promises.push(makeAPromise(value)); }); // Consolidate all promises into a promise of promises return Q.all(promises); }; startParallelActions ().then( . . .
下面的地址, http://jsfiddle.net/mauget/XKCy2/,针对JSFiddle在浏览器中运行十个并行promises,随机的拒绝或通过。这里有完整的代码用于检查和变化if条件。重新运行,直到你得到一个相反的完成。
孕育 Promise
许多api返回的promise都有一个then函数——他们是thenable。通常我只是通过then处理thenable函数的结果。然而,$q,mpromise,和kew库拥有同样的API用于创建,拒绝,或者通过promise。这里有API文档链接到每个库的引用部分。我通常不需要构造一个promise,除了本文中的包装promise的未知描述和timeout函数。请参考哪些我创建的promises。
Promise库互操作
大多数JavaScript promise库在then级别进行互操作。你可以从一个外部的promise创建一个promise,因为promise可以包装任何类型的值。then可以支持跨库工作。除了then,其他的promise函数则可能不同。如果你需要一个你的库不包含的函数,你可以将一个基于你的库的promise包装到一个新的,基于含有你所需函数的库创建的promise里面。例如,JQuery的promise有时为人所诟病。那么你可以将其包装到Q,$q,mpromise,或者kew库的promise中进行操作。
结语
现在我写了这篇文章,而一年前我却是犹豫要不要拥抱promise的那个。我只是单纯地想完成一项工作。 我不想学习新的API,或是打破我原来的代码(因为误解了promise)。我曾经如此错误地认为!当我下了一点注时,就轻易就赢得了可喜的成果。
在这篇文章中,我已经简单给出了一个单一的promise,promise链,和一个并行的promise的promise的的例子。 Promises不难使用。如果我可以使用它们,任何人都可以。 要查看完整的概念,我支持你点击专家写的参考指南。从Promises/A 的参考开始,从事实上的标准JavaScript的Promise 开始。