多くの言語には、非同期パターンを通常のシーケンスのように処理するために、Promise、Deferred、または Future と呼ばれる興味深いソリューションのライブラリが含まれています。 JavaScript の約束により、密結合されたインターフェイスの代わりに懸念事項の分離が促進されます。 この記事では、Promises/A 標準に基づいた JavaScript Promise について説明します。 [http://wiki.commonjs.org/wiki/Promises/A]
プロミスのユースケース:
JavaScript Promise は、将来値を返すことを約束するオブジェクトです。明確に定義された動作を持つデータ オブジェクトです。 Promise には 3 つの可能な状態があります:
拒否または完了した Promise は解決されたとみなされます。 Promise は保留中から解決済みにのみ移行できます。その後、約束の状態は変わりません。 Promise は、対応する処理が完了した後も長く存在することができます。つまり、処理結果を複数回取得することができます。この関数は、promise.then() を呼び出すことで結果を取得します。この関数は、promise に対応する処理が完了するまで戻りません。たくさんの約束を柔軟につなぎ合わせることができます。これらの連結された「then」関数は、新しい Promise または最も古い Promise を返す必要があります。
このスタイルを使用すると、同期コードを作成するのと同じように非同期コードを作成できます。主に、Promise を組み合わせることで実現されます:
なぜわざわざ?基本的なコールバック関数を使用することはできないでしょうか?
コールバック関数の問題
コールバック関数は、クリックに基づいてフォームを検証したり、REST 呼び出しの結果を保存したりするなど、単純な繰り返しイベントに適しています。コールバック関数は、1 つのコールバック関数が REST 関数を呼び出し、その REST 関数に対して新しいコールバック関数を設定し、この新しいコールバック関数が別の REST 関数を呼び出すという一連のコード チェーンも作成します。コードの水平方向の増加は垂直方向の増加よりも大きくなります。コールバック関数は、結果が必要になるまでは単純に見えますが、次の行の計算に使用するためにすぐに結果が必要になります。
'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();
タイムアウトを使用して非同期操作をシミュレートします。例外を管理する方法は面倒であり、ダウンストリームの動作を簡単に悪用する可能性があります。コールバックを記述すると、コード構成が混乱してしまいます。図 2 は、NodeJS REPL で実行できる模擬検証フローを示しています。次のセクションでは、破滅のピラミッド パターンから継続的な約束に移ります。
図
'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();
NodeJS REPL での実行結果
$ node scripts/examp2b.js 1 Wait for it ... 2 result first 3 result second 4 result third 5 result fourth $
私はかつて、AngularJS の動的検証が、対応するテーブルの値に基づいてフォーム項目の値を動的に制限する状況に遭遇しました。制限項目の有効な値の範囲はRESTサービス上で定義されます。
コールバックのネストを避けるために、要求された値に従って関数スタックを操作するスケジューラーを作成しました。スケジューラはスタックから関数をポップして実行します。関数のコールバックは、スタックがクリアされるまで、最後にスケジューラを再度呼び出します。各コールバックは、リモート検証呼び出しから返されたすべての検証エラーをログに記録します。
私が書いたことはアンチパターンだと思います。 Angular の $http 呼び出しによって提供される Promise を使用すると、検証プロセス全体での思考が同期プログラミングのようにより線形になります。フラット化されたプロミスチェーンは読み取り可能です。続けて...
Promise を使用する
Kew Promise ライブラリが使用されます。同じことが Q ライブラリにも当てはまります。このライブラリを使用するには、まず npm を使用して kew ライブラリを NodeJS にインポートし、次にコードを NodeJS REPL にロードします。
図
'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();
出力は、ネストされたコールバックを使用した場合と同じです:
$ 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 开始。