このセクションではDeferredとPromiseの関係について簡潔に学んでいきます。
Deferredという単語はPromiseと同じコンテキストで聞いたことがあるかもしれません。 有名な所だと jQuery.Deferred や JSDeferred 等があげられるでしょう。
DeferredはPromiseと違い、共通の仕様があるわけではなく、各ライブラリがそのような目的の実装をそう呼んでいます。
今回は jQuery.Deferred のようなDeferredの実装を中心にして話を進めます。
DeferredとPromiseの関係を簡単に書くと以下のようになります。
-
Deferred は Promiseを持っている
-
Deferred は Promiseの状態を操作する特権的なメソッドを持っている
この図を見ると分かりますが、DeferredとPromiseは比べるような関係ではなく、 DeferredがPromiseを内蔵しているような関係になっていることが分かります。
Note
|
jQuery.Deferredの構造を簡略化したものです。Promiseを使わないDeferredの実装もあります。 |
図だけだと分かりにくいので、実際にPromiseを使ってDeferredクラスを実装してみましょう。
Note
|
ECMAScript 2015ではクラスを定義する |
Promiseの上にDeferredクラスを実装した例です。
link:embed/embed-deferred.js[role=include]
以前Promiseを使って実装したfetchURL
をこのDeferredで実装しなおしてみます。
link:embed/embed-xhr-deferred.js[role=include]
// 実行例
const URL = "https://httpbin.org/get";
fetchURL(URL).then(function onFulfilled(value){
console.log(value);
}).catch(console.error.bind(console));
Promiseの状態を操作する特権的なメソッドというのは、 promiseオブジェクトの状態をresolve、rejectすることができるメソッドで、 通常のPromiseだとコンストラクタで渡した関数の中でしか操作することができません。
通常のPromiseで実装したものと見比べていきたいと思います。
link:../Ch1_WhatsPromises/embed/embed-xhr-promise.js[role=include]
// 実行例
const URL = "https://httpbin.org/get";
fetchURL(URL).then(function onFulfilled(value){
console.log(value);
}).catch(console.error.bind(console));
2つの fetchURL
を見比べて見ると以下のような違いがあることが分かります。
-
Deferred の場合は全体がPromiseで囲まれていない
-
関数で囲んでないため、1段ネストが減っている
-
Promiseコンストラクタの中で処理が行われていないため、自動的に例外をキャッチしない
-
逆に以下の部分は同じことをやっています。
-
全体的な処理の流れ
-
resolve
、reject
を呼ぶタイミング
-
-
関数はpromiseオブジェクトを返す
このDeferredはPromiseを持っているため、大きな流れは同じですが、 Deferredには特権的なメソッドを持っていることや自分で流れを制御する裁量が大きいことが分かります。
たとえば、Promiseの場合はコンストラクタの中に処理を書くことが通例なので、
resolve
、reject
を呼ぶタイミングが大体みて分かります。
new Promise((resolve, reject) => {
// この中に解決する処理を書く
});
一方Deferredの場合は、関数的なまとまりはないのでdeferredオブジェクトを作ったところから、
任意のタイミングで resolve
、reject
を呼ぶ感じになります。
const deferred = new Deferred();
// どこかのタイミングでdeferred.resolve or deferred.rejectを呼ぶ
このように小さなDeferredの実装ですがPromiseとの違いが出ていることが分かります。
これは、Promiseが値を抽象化したオブジェクトなのに対して、 Deferredはまだ処理が終わってないという状態や操作を抽象化したオブジェクトである違いがでているのかもしれません。
言い換えると、 Promiseはこの値は将来的に正常な値(Fulfilled)か異常な値(Rejected)が入るというものを予約したオブジェクトなのに対して、 Deferredはまだ処理が終わってないということを表すオブジェクトで、 処理が終わった時の結果を取得する機構(Promise)に加えて処理を進める機構をもったものといえるかもしれません。
より詳しくDeferredについて知りたい人は以下を参照するといいでしょう。
Note
|
DeferredはPythonの Twisted というフレームワークが最初に定義した概念です。 JavaScriptへは MochiKit.Async 、 dojo/Deferred 等のライブラリがその概念を持ってきたと言われています。 |