ハッカーを目指す白Tのブログ

夏は白のTシャツを着ています。ラジオが好きです。(ラジオネーム: 隠れキリジダン)

jQueryのPromiseとDeferredとは

javascriptで非同期処理がからむと、コールバックが乱立してしまい、エラーに対する例外処理を正確に書くことが難しくなった経験はないだろうか。今回は、JavaScript複数の連続する非同期処理を上手く扱うためのjQueryの標準モジュールであるjQuery.Deferredについて、その概念や使い方を解説していく。

1. jQuery.Deferredとは

jQuery.Deferredは、複数の連続する非同期処理を単純で分かり易く記述できるようにすると述べたが、それはどのようにして行われるのか。それは、複数の非同期処理ひとつひとつにPromiseオブジェクトを割り当て、それを伝播させていくことで実現される。それでは、Promiseオブジェクトとは何か。Promiseオブジェクトは、割り当てられた非同期処理が成功しているか否か等の状態の情報を持つオブジェクトである。その状態の種類は3つあり、Deferred.state()で確認することができる。

Promiseオブジェクトの状態(Deferred.state()の返り値)

1. pending : 処理が未完了。
2. resolved : 処理が成功
3. rejected : 処理が失敗

Promiseオブジェクトは、生成されたときはpendingの状態で、割り当てられた処理が完了した際に、処理が成功した場合はresolved、処理が失敗した場合はrejectedに状態に変わる。そして、resolvedまたはrejectedの状態のPromiseオブジェクトがreturnされたときに実行されるコールバックは、以下の2つのメソッドで指定できる。(なお、Promiseオブジェクトの状態の変化やPromiseオブジェクトのreturnは、ajaxやGETjsonなどのjQueryの標準の非同期処理には実装されているが、それ以外の非同期処理においては、手動で実装しなければならない。)

1.状態がresolvedのPromiseオブジェクトがreturnされたときのコールバックは、.done()メソッドで呼び出される。
2.状態がrejectedのPromiseオブジェクトがreturnされたときのコールバックは、.fail()メソッドで呼び出される。

さて、Promiseオブジェクトは、Deferredオブジェクトに内包されるものである。よって、Deferredオブジェクトのresolveメソッドを呼び出すと、内包するPromiseオブジェクトの状態がresolvedになり、Deferredオブジェクトのrejectメソッドを呼び出すと、内包するPromiseオブジェクトの状態がrejectedになる。

//1秒後にHello!を出力するDeferred対応関数。必ずresolveする

function delayHello()
{
  var d = new $.Deferred;     //Deferredオブジェクトを生成
  setTimeout(function(){
    console.log('Hello!');
    d.resolve();       //Promiseオブジェクトの状態をresolvedに変更
  }, 1000);
  return d.promise();    //Promiseオブジェクトをreturn
}

//1秒後にエラーを発生させるDeferred対応関数。必ずrejectする

function delayError() {
  var d = new $.Deferred;    //Defferedオブジェクトを生成
  setTimeout(function(){
    d.reject('Error!!');              //Promiseオブジェクトの状態をrejectedに変更
  }, 1000);
  return d.promise();            //Promiseオブジェクトをreturn
}

//.done()と.fail()

var promise = delayHello();
promise.done(function(){ /* resolvedで実行 */ }); //.done()メソッドによるコールバックが実行される
promise.fail(function(e){ /* rejectedで実行 */ });

//本題とは、関係ないがfailのメソッドで定義されているコールバック(function(e){ /* rejectedで実行 */ })において、関数の引数に引き渡されているeは、Errorオブジェクトである。

2. .then()メソッドと.when()メソッド

ここで、jQuery.Deferredの一番の特徴とも言える、.then()と$.when()について解説する。これらは、複数の連続する非同期処理の連結、その例外処理を驚くほど簡単にする。まず、.then()メソッドは、Promiseオブジェクトを返す非同期処理に対して呼び出すことで、非同期処理が成功または失敗した場合の両方に対するコールバックを指定できる。.then()メソッドの第一引数に成功した場合、第二引数に失敗した場合の処理を記述する。この.then()メソッドは、.done()メソッドや.fail()メソッドと違い、新たにPromiseオブジェクトを生成し、returnするので、非同期処理を連続させことができる。

delayHello()
.then(function(){ /* resolvedで実行 */ }, function(e){ /* rejectedで実行 */ }

//↓これは直列連結の書き方として間違い
delayHello().done(delayHello).done(delayHello); //1つめのdelayHello()がPromiseオブジェクトをreturnしたタイミングで、ふたつのコールバックが同時に呼び出されることになる

また、$.when()を使うことで、複数の非同期処理を並行して実行することができる。$.when()は、併記したDeferredの非同期処理の全てが、resolvedのPromiseオブジェクトをreturnした場合にのみ、statusがresolvedの新たなPromiseオブジェクトをreturnする。また、併記した非同期処理の中で、ひとつでもrejectedのPromiseオブジェクトをreturnするものがあった場合は、statusがrejectedの新たなPromiseオブジェクトをreturnする。$.when()も、新たにPromiseオブジェクトを生成し、returnしているので、非同期処理を連続して書くことができる。

$.when(delayHello(), delayHello(), delayHello())
.done(/*全てresolvedで実行*/);    //全てresolvedのPromiseオブジェクトを返すので実行される

.then()と$.whenによる非同期処理の連結は、その処理の順番を容易に変えることができることも大きなメリットである。

3. 連続する非同期処理の関数化

jQuery.Deferredは、さらに連続する非同期処理を関数化してまとめることができる。これによって、コードの可読性が増し、また再利用が可能になる。
以下のように、複雑な処理も、関数化することによって格段に、読みやすく、再利用しやすいコードになる。

delayHello()
.then(delayHello)
.then(function(){
  return $.when(delayHello(), delayHello(), delayHello());
})
.then(delayHello)
.then(function(){
  return $.when(delayHello(), delayHello(), delayHello());
})
.then(delayHello);

関数化すると、

//一部分だけ抜き出して関数化
function delayHelloParallel() {
  return $.when(
    delayHello(), delayHello(), delayHello()
  )
  .then(delayHello);
}

//delayHelloParallelを使って少々簡略化
delayHello()
.then(delayHello)
.then(delayHelloParallel)
.then(delayHelloParallel)

4. jQuery.Deferredを使う場面

それでは、どんな時にjQuery.Deferredを使うべきなのか。
それは、JavaScript の非同期処理を、順序立てて、連続して、複数回実行したいときである。
例えば、キーワードとして検索した住所の気象情報を取得する処理をjQueryで実装したい場合、
1. Yahoo!ジオコーダAPIで、キーワードとして検索した住所の緯度経度を取得し、
2. 1のAPIのレスポンスが返ってき次第、その緯度経度をもとに、気象情報APIで、気象情報を取得する
必要がある。この際、1の処理が終わってから、2の処理と、順番に処理を実行する必要があり、jQuery.Deferred を使うことで、この一連の処理を簡単に、分かり易く、書くことができる。

jQueryの代表的な非同期処理であるajaxについては、以下を参照して頂きたい。

参照URL


爆速でわかるjQuery.Deferred超入門 - Yahoo! JAPAN Tech Blog
jQueryのDeferredとPromiseで応答性の良いアプリをー基本編 | ゆっくりと…
結局jQuery.Deferredの何が嬉しいのか分からない、という人向けの小話 - Qiita

以上