JavaScript非同期プログラミングにおけるjQueryのpromiseオブジェクトの役割の詳細

10443 ワード

Promise、中国語は願望と理解でき、単一の操作が完了した最終結果を表す.1つのPromiseには、unfulfilled(満たされていない)、fulfilled(満たされている)、failed(失敗した)、fulfilledステータス、failedステータスの3つのステータスがあります.1つの願望が満たされていない状態から満足または失敗状態に変化することができ、1つの願望が満たされているか失敗している状態になると、その状態はこれ以上変化しない.この「変更不可」特性は、PromiseのステータスリスナーがPromiseのステータスを変更して他のリスナーの動作異常を引き起こすことを回避するPromiseにとって非常に重要である.たとえば、fulfilledステータスをリスニングするリスナーがPromiseのステータスをfailedに変更すると、failedステータスのリスナーがトリガーされ、failedステータスのリスナーがPromiseのステータスをfulfilledに設定すると、fulfilledステータスのリスナーがトリガーされ、デッドサイクルになります.もう1つのPromiseという特性を理解する方法は、Promiseをjavascriptのprimativeタイプの変数と見なすことであり、この変数は呼び出された関数に伝達されてもよいが、呼び出された関数によって変更されてはならない.
各Promiseオブジェクトには、Promiseの異なるステータスをリスニングするためのthen(fulfilledHandler,errorHandler,progressHandler)という方法があります.fulfilledHandlerはfulfilledイベントをリスニングするために使用され、errorHandlerはfailedイベントをリスニングするために使用され、progressHandlerはprogressイベントをリスニングするために使用される.Promiseはprogress状態のイベントリスニングを強制しない(jQueryのDeferredはPromiseの実装であるが、progress状態イベントの処理は実現していない).
then(...)関数のfulfilledHandlerとerrorHandlerの戻り値は、then(...)をチェーンで呼び出すための新しいPromiseオブジェクトです.で行ないます.各コールバック関数は、通常はfulfilled状態のPromiseを返します.コールバック関数がエラー値を返すと、返されるPromise状態はfailedになります.
promiseの非同期プログラミングにおける役割
非同期モードはウェブプログラミングにおいてますます重要になり、ウェブの主流言語であるJavascriptにとって、このモードの実現は容易ではないため、多くのJavascriptライブラリ(例えばjQueryやDojo)はpromiseと呼ばれる抽象(deferredとも呼ばれることもある)を追加した.これらのライブラリにより,開発者は実際のプログラミングでpromiseモードを使用することができる.Web 2.0技術の深化に伴い、ブラウザ側はますます多くの計算圧力に耐えているため、「同時」は積極的な意義を持っている.開発者にとって,ページとユーザのインタラクションを影響を受けないようにするとともに,ページと非同期タスクの関係を調整するという非線形実行のプログラミング要件に適応する困難がある.ページインタラクションはともかく,非同期呼び出しには成功操作と失敗処理の2つの結果を処理する必要があることが考えられる.正常に呼び出された後、返された結果を別のAjaxリクエストに使用する必要がある場合があります.これにより、「関数ループ」が発生します.この場合、プログラミングの複雑さをもたらします.次のコード例を見てみましょう(XMLHttpRequest 2ベース):

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のhashtagがIE 10とIE 9の内容を取得し、ページに表示する機能です.このネストされたコールバック関数は理解しにくいが,開発者はどのコードが応用に用いられるビジネスロジックを注意深く分析し,どのコードが非同期関数呼び出しを処理し,コード構造が支離滅裂であるかを分析する必要がある.エラー処理も分解され,各箇所でエラーの発生を検出し,対応する処理を行う必要がある.
非同期プログラミングの複雑さを低減するために,開発者は非同期操作を処理するための簡便な方法を探してきた.1つの処理モードはpromiseと呼ばれ、長時間稼働する可能性があり、必ずしも完全な操作を必要としない結果を表す.このモードは、長時間の操作の完了をブロックしたり待つのではなく、承諾された結果を表すオブジェクトを返します.
このような例を考慮すると、ページコードはサードパーティのAPIにアクセスする必要があり、ネットワーク遅延により応答時間が長くなる可能性があります.この場合、非同期プログラミングを採用することは、ページ全体とユーザーのインタラクションに影響を与えません.promiseモードは、通常、状態変化時に対応するコールバック関数を登録するためのthenと呼ばれる方法を実現する.たとえば、次のコードの例を示します.

searchTwitter(term).then(filterResults).then(displayResults);

promiseモードは、未完了(unfulfilled)、完了(resolved)、拒否(rejected)の3つの状態のいずれかです.CommonJS Promise/A規格を例にとると、promiseオブジェクト上のthenメソッドは、完了および拒否された状態での処理関数の追加を担当します.thenメソッドは、promiseパイプを形成するために別のpromiseオブジェクトを返します.このpromiseオブジェクトを返す方法は、then(resolvedHandler、rejectedHandler)のような非同期操作を開発者が直列に接続することをサポートします.resolvedHandlerコールバック関数はpromiseオブジェクトが完了状態に入るとトリガーされ、結果が伝達されます.rejectedHandler関数は拒否状態で呼び出されます.
promiseモードがあれば、上記のTwitterの例を再実現することができます.実装方法をよりよく理解するために,ゼロからpromiseモードのフレームワークを構築することを試みた.まずpromiseを格納するオブジェクトが必要です.

var Promise = function () {
    /* initialize promise */
  };

次にthenメソッドを定義し、完了ステータスと拒否ステータスを処理するために2つのパラメータを受け入れます.

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の棚を構築し、IE 10のコンテンツのみを取得すると仮定して、上記の例を続行することができます.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);
}


これまで,promiseモードを単一のAjaxリクエストに適用することができたが,promiseの優位性はまだ現れていないようだ.複数のAjaxリクエストの同時コラボレーションを見てみましょう.この場合、呼び出しの準備をするpromiseオブジェクトを格納する別の方法whenが必要です.あるpromiseが完了していない状態を完了または拒否状態に変換すると、thenメソッドの対応する処理関数が呼び出されます.whenメソッドは、すべての操作が完了するのを待つ必要がある場合に重要です.

Promise.when = function () {
  /* handle promises arguments and queue each */
};

先ほどIE 10とIE 9の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関数は2つの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モードに対して1つの比較的に完全な理解があって、非同期のプログラミングはますます重要になって、このような情況の下で、私達は方法を見つけて複雑さを下げる必要があって、promiseモードは1つのとても良い例で、その風格は比較的に人間化して、その上主流のJSフレームワークは自分の実現を提供しました.プログラミングの実践の中で、開発者はこのような便利なプログラミング技術を試しなければならない.なお、promiseモードの使用には、promiseオブジェクトを適切に設定し、対応するイベントで状態変換関数を呼び出し、最後にpromiseオブジェクトを返す必要がある.