非同期モードは、Web プログラミングにおいてますます重要になってきています。主流の Web 言語である Javascript では、このモードを実装するのはそれほど簡単ではありません。このため、多くの Javascript ライブラリ (jQuery や Dojo など) には、Promise 抽象化と呼ばれるメソッドが追加されています。 (遅延とも呼ばれることもあります)。これらのライブラリを通じて、開発者は実際のプログラミングで Promise パターンを使用できます。 IE 公式ブログは最近、XMLHttpRequest2 を使用して Promise モードを実践する方法を詳しく説明した記事を公開しました。関連する概念とアプリケーションを見てみましょう。
Web ページに (XMLHttpRequest2 または Web Workers を介した) 非同期操作がある例を考えてみましょう。 Web 2.0 テクノロジの深化に伴い、ブラウザ側のコンピューティング負荷はますます増大しているため、「同時実行性」は肯定的な意味を持ちます。開発者にとって、ページとユーザー間の対話に影響を与えないようにするだけでなく、この種の非線形実行プログラミング要件は、適応するのに困難を伴います。ページの対話はさておき、非同期呼び出しで処理する必要がある 2 つの結果、つまり成功した操作と失敗した処理が考えられます。呼び出しが成功した後、返された結果を別の Ajax リクエストで使用する必要がある場合があります。これにより、「関数のチェーン」状況が発生します (著者の別の記事「NodeJS の非同期プログラミング スタイル」の説明で詳しく説明されています)。この状況により、プログラミングが複雑になります。次のコード例を見てください (XMLHttpRequest2 に基づく):
function searchTwitter(term, onload, onerror) { var xhr, results, url; url = 'http://search.twitter.com/search.json?rpp=100&q=' + term; xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.onload = function (e) { if (this.status === 200) { results = JSON.parse(this.responseText); onload(results); } }; xhr.onerror = function (e) { onerror(e); }; xhr.send(); } function handleError(error) { /* handle the error */ } function concatResults() { /* order tweets by date */ } function loadTweets() { var container = document.getElementById('container'); searchTwitter('#IE10', function (data1) { searchTwitter('#IE9', function (data2) { /* Reshuffle due to date */ var totalResults = concatResults(data1.results, data2.results); totalResults.forEach(function (tweet) { var el = document.createElement('li'); el.innerText = tweet.text; container.appendChild(el); }); }, handleError); }, handleError); }
上記のコードの機能は、Twitter でハッシュタグ IE10 および IE9 を持つコンテンツを取得し、ページに表示することです。この種のネストされたコールバック関数を理解するのは困難です。開発者は、どのコードがアプリケーションのビジネス ロジックに使用されているか、どのコードが非同期関数呼び出しを処理しているかを注意深く分析する必要があります。コード構造は断片化されています。エラー処理も分解され、さまざまな場所でエラーの発生を検出し、それに応じて処理する必要があります。
非同期プログラミングの複雑さを軽減するために、開発者は非同期操作を処理する簡単な方法を探してきました。これらの処理パターンの 1 つはプロミスと呼ばれ、長時間実行される可能性があり、必ずしも完了する必要はない操作の結果を表します。このパターンは、ブロックして長い操作が完了するのを待つ代わりに、約束された結果を表すオブジェクトを返します。
ページ コードがサードパーティ API にアクセスする必要がある例を考えてみましょう。この場合、非同期プログラミングを使用しても、ページ全体とユーザー間の対話には影響しません。通常、Promise モードは、状態が変化したときに対応するコールバック関数を登録するために呼び出されるメソッドを実装します。たとえば、次のコード例は次のとおりです。
searchTwitter(term).then(filterResults).then(displayResults);
Promise モードは常に、未履行、解決済み、拒否の 3 つの状態のいずれかになります。 CommonJS Promise/A 標準を例に挙げると、Promise オブジェクトの then メソッドは、完了状態と拒否状態の処理関数を追加する役割を果たします。 then メソッドは、Promise パイプラインの形成を容易にするために別の Promise オブジェクトを返します。このメソッドにより、開発者は then(resolvedHandler, requestedHandler); などの非同期操作をチェーンすることができます。 solvedHandler コールバック関数は、Promise オブジェクトが完了状態になり、拒否状態で呼び出された結果を渡すときにトリガーされます。
Promise パターンを使用すると、上記の Twitter の例を再実装できます。実装方法をより深く理解するために、Promise パターン フレームワークを一から構築してみました。まず、Promise を保存するためのオブジェクトが必要です。
var Promise = function () { /* initialize promise */ };
次に、完了ステータスと拒否ステータスを処理するための 2 つのパラメーターを受け入れる then メソッドを定義します。
Promise.prototype.then = function (onResolved, onRejected) { /* invoke handlers based upon state transition */ };
同時に、未完了から完了へのステータス遷移と、未完了から拒否へのステータス遷移を実行するには 2 つのメソッドが必要です。
Promise.prototype.resolve = function (value) { /* move from unfulfilled to resolved */ }; Promise.prototype.reject = function (error) { /* move from unfulfilled to rejected */ };
Promise シェルフを構築したので、IE10 のコンテンツのみを取得すると仮定して、上記の例を続けることができます。 Ajax リクエストを送信するメソッドを作成し、それを Promise でラップします。このpromiseオブジェクトは、xhr.onloadとxhr.onerrorでそれぞれ完了と拒否ステータスの遷移処理を指定します。searchTwitter関数はpromiseオブジェクトを返すことに注意してください。次に、loadTweets で then メソッドを使用して、完了ステータスと拒否ステータスに対応するコールバック関数を設定します。
function searchTwitter(term) { var url, xhr, results, promise; url = 'http://search.twitter.com/search.json?rpp=100&q=' + term; promise = new Promise(); xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.onload = function (e) { if (this.status === 200) { results = JSON.parse(this.responseText); promise.resolve(results); } }; xhr.onerror = function (e) { promise.reject(e); }; xhr.send(); return promise; } function loadTweets() { var container = document.getElementById('container'); searchTwitter('#IE10').then(function (data) { data.results.forEach(function (tweet) { var el = document.createElement('li'); el.innerText = tweet.text; container.appendChild(el); }); }, handleError); }
今のところ、単一の Ajax リクエストに Promise モードを適用できますが、Promise のメリットが反映されていないようです。複数の Ajax リクエストの同時コラボレーションを見てみましょう。この時点で、呼び出せるように Promise オブジェクトを保存する別のメソッドが必要になります。 Promise が不完全な状態から完了または拒否された状態に変換されると、then メソッド内の対応するハンドラー関数が呼び出されます。 when メソッドは、すべての操作が完了するまで待機する必要がある場合に重要です。
Promise.when = function () { /* handle promises arguments and queue each */ };
IE10 と IE9 から 2 つのコンテンツを取得するシナリオを例として、次のようなコードを書くことができます:
var container, promise1, promise2; container = document.getElementById('container'); promise1 = searchTwitter('#IE10'); promise2 = searchTwitter('#IE9'); Promise.when(promise1, promise2).then(function (data1, data2) { /* Reshuffle due to date */ var totalResults = concatResults(data1.results, data2.results); totalResults.forEach(function (tweet) { var el = document.createElement('li'); el.innerText = tweet.text; container.appendChild(el); }); }, handleError);
分析上面的代码可知,when函数会等待两个promise对象的状态发生变化再做具体的处理。在实际的Promise库中,when函数有很多变种,比如 when.some()、when.all()、when.any()等,读者从函数名字中大概能猜出几分意思来,详细的说明可以参考CommonJS的一个promise实现when.js。
除了CommonJS,其他主流的Javascript框架如jQuery、Dojo等都存在自己的promise实现。开发人员应该好好利用这种模式来降低异步编程的复杂性。我们选取Dojo为例,看一看它的实现有什么异同。
Dojo框架里实现promise模式的对象是Deferred,该对象也有then函数用于处理完成和拒绝状态并支持串联,同时还有resolve和reject,功能如之前所述。下面的代码完成了Twitter的场景:
function searchTwitter(term) { var url, xhr, results, def; url = 'http://search.twitter.com/search.json?rpp=100&q=' + term; def = new dojo.Deferred(); xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.onload = function (e) { if (this.status === 200) { results = JSON.parse(this.responseText); def.resolve(results); } }; xhr.onerror = function (e) { def.reject(e); }; xhr.send(); return def; } dojo.ready(function () { var container = dojo.byId('container'); searchTwitter('#IE10').then(function (data) { data.results.forEach(function (tweet) { dojo.create('li', { innerHTML: tweet.text }, container); }); }); });
不仅如此,类似dojo.xhrGet方法返回的即是dojo.Deferred对象,所以无须自己包装promise模式。
var deferred = dojo.xhrGet({ url: "search.json", handleAs: "json" }); deferred.then(function (data) { /* handle results */ }, function (error) { /* handle error */ });
除此之外,Dojo还引入了dojo.DeferredList,支持开发人员同时处理多个dojo.Deferred对象,这其实就是上面所提到的when方法的另一种表现形式。
dojo.require("dojo.DeferredList"); dojo.ready(function () { var container, def1, def2, defs; container = dojo.byId('container'); def1 = searchTwitter('#IE10'); def2 = searchTwitter('#IE9'); defs = new dojo.DeferredList([def1, def2]); defs.then(function (data) { // Handle exceptions if (!results[0][0] || !results[1][0]) { dojo.create("li", { innerHTML: 'an error occurred' }, container); return; } var totalResults = concatResults(data[0][1].results, data[1][1].results); totalResults.forEach(function (tweet) { dojo.create("li", { innerHTML: tweet.text }, container); }); }); });
上面的代码比较清楚,不再详述。
说到这里,读者可能已经对promise模式有了一个比较完整的了解,异步编程会变得越来越重要,在这种情况下,我们需要找到办法来降低复杂度,promise模式就是一个很好的例子,它的风格比较人性化,而且主流的JS框架提供了自己的实现。所以在编程实践中,开发人员应该尝试这种便捷的编程技巧。需要注意的是,promise模式的使用需要恰当地设置promise对象,在对应的事件中调用状态转换函数,并且在最后返回promise对象。
技术社区对异步编程的关注也在升温,国内社区也发出了自己的声音。资深技术专家老赵就发布了一套开源的异步开发辅助库Jscex,它的设计很巧妙,抛弃了回调函数的编程方式,采用一种“线性编码、异步执行”的思想,感兴趣的读者可以查看这里。
不仅仅是前端的JS库,如今火热的NodeJS平台也出现了许多第三方的promise模块,具体的清单可以访问这里。
以上がJavaScript非同期プログラミングのPromiseパターンサンプルコードの詳細紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。