メインコンテンツへ移動

Promise を完全に理解する

無料2015-11-15#JS#Promise#Promise A+#Promise a plus#js promise#jquery promise#JavaScript Promise#Promise教程#Promise怎么用

畏敬から疑惑へ、推測から恍然大悟へ、「何 - え -难道-哦~~」、本文は Promise の内部メカニズムを詳細に説明します

前置き

単語の対応する翻訳に注意し、パターン全体の理解に役立てます

  • promise: 約束

  • pending: 待定

  • settled: 定了

  • resolve(d): (已)解決

  • reject(ed): (已)拒否

  • onFulfilled: 履行済み(イベント)

  • onRejected: 拒否済み(イベント)

  • then: その後、次に

  • catch: キャッチ

  • race: 競争

  • future: 未来(値)

  • thenable:then 可能(then 属性を持つ)

一.Promise とは何か

非同期操作処理メカニズムの一套、Promise A+ は仕様であり、jQuery、ES2015、q の promise はすべてこの仕様の実装です

二.Promise の特徴

  • 非同期操作の実行順序をより簡単に制御できる

  • 一連の非同期操作で、エラーを柔軟に処理できる

  • チェーン呼び出しをサポートし、書きやすい

  • コールバックピラミッドを解消でき、見やすい

三.Promise の用途

  • 直列タスクキューの実装

  • 直列タスクパイプラインの実装

  • 実行時になって初めて実行順序が確定する直列タスクの処理

(篇幅の制限により、具体的な適用シーンは後で展開)

四.Promise タイプ

内部属性 [[PromiseStatus]]/[[PromiseValue]]

####[[PromiseStatus]]: pending(待定) | resolved(已解決) | rejected(已拒否)

後の 2 つを総称して settled(定了)、pending は初期状態で、new Promise(func) 後、func 中の resolvereject がまだ実行されていない場合、この時 status=pending

####[[PromiseValue]]: undefined | resolve/reject のパラメータ | Promise.resolve/reject の!thenable パラメータ | onFulfilled/onRejected の戻り値

then が呼び出されると、この値は thenonFulfilled または onRejected に渡され、resolvereject が始終実行されない場合、thenonFulfilledonRejected も始終実行されません

コンストラクタパラメータ resolve/reject

####resolve(解決)

resolve を実行後、thenonFulfilled コールバックをトリガーし、resolve のパラメータを onFulfilled に渡します

####reject(拒否)

reject を実行後、then | catchonRejected コールバックをトリガーし、reject のパラメータを onRejected に渡します

注意:直後の thenonRejected コールバックを宣言していない場合、次の then | catchonRejected に渡され、まだない場合、さらに渡す...最後までなければ、エラー Uncaught (in promise) Error:xxx を報告

特殊的resolve のパラメータが Promise オブジェクトの場合、該オブジェクトの最終的な [[PromiseValue]] は外層 Promise オブジェクト後続の thenonFulfilled/onRejected に渡されます。例えば:

// 外層 then は内層 future と error を取得可能
new Promise(function(resolve, reject) {
    resolve(new Promise(function(resolve, reject) {
        resolve(12);
        // reject(new Error('reject in nested Promise'));
    }).then(function(future) {
        return future + 1;
    }, function(error) {
        return new Error('reject again in nested Promise');
    }));
}).then(onFulfilled, onRejected);
// => 13 onFulfilled が内層 promise の最終的な [[PromiseValue]] を受け取ったため
// reject の場合、onFulfilled は Error('reject again') を受け取る

五.基本構文

###1.Promise オブジェクトの作成

Promise を作成する基本構文は以下の通り:

// 作成
var promise = new Promise(function(resolve, reject) {
    if (/*...*/) {
        resolve(data);
    }
    else {
        reject(new Error('error occurs'));
    }
});
// 使用
// then
promise.then(function(future) {
    // ...
}, function(error) {
    // ...
});
// catch
promise.catch(function(error) {
    // ...
});

注意new Promise(func) 時、func即時実行されます(generator が宣言のみで実行しないのとは異なり)、実行過程中 resolve() に遭遇しても見ないふり(P.S.簡単に見ないふりと理解、解説は注釈参照)、reject() に遭遇すると即時エラーをスロー(注意:Chrome46 は即時エラーをスロー、FF39 はエラーなし、其它 polyfill、例:es6-promisepromise-polyfill はエラーなし)、対比例は以下の通り:

new Promise(function(resolve, reject) {
    console.log('#1');
    resolve(1);
    console.log('#2');
});
// => #1 #2 エラーなし(resolve に遭遇しても見ないふり)
// 実際には内部属性 [[PromiseStatus]] と [[PromiseValue]] は変化した但、then が提供する onFulfilled コールバックがないため、効果が見えない

new Promise(function(resolve, reject) {
    console.log('#1');
    reject(new Error('reject'));
    console.log('#2');
});
// => #1 #2 Chrome46 はエラーを報告:Uncaught (in promise) Error: reject(…)
// 後続に then/catch が提供する onRejected コールバックがないため、例外が消費されていない

P.S.reject のパラメータは任意のタイプでよいが、一般にエラー原因を示す Error オブジェクトを渡す

###2.Promise.prototype.then(onFulfilled, onRejected) その後

promiseonFulfilled/onRejected コールバック handler を追加し、promiseresolve/reject が実行された時に対応する handler をトリガー

注意promise.then でコールバック関数を追加する時、promise がすでに fulfilled または rejected 状態にある場合、対応するメソッドは即時呼び出されます

Promise オブジェクトを返す(チェーン呼び出しが可能)、簡単に以下のように理解:

  1. onFulfilled がトリガーされた場合、該オブジェクトの [[PromiseValue]]=onFulfilled の戻り値、[[PromiseStatus]]=resolved

    new Promise(function(resolve, reject) { resolve(2); }).then(function(future) { return future * 2; }, onRejected).then(onFulfilled, onRejected); // onFulfilled が 4 を取得

  2. onRejected がトリガーされた場合、該オブジェクトの [[PromiseValue]]=onRejected の戻り値、[[PromiseStatus]]=resolved

特に注意:返されるオブジェクトの状態は resolved で、rejectedではない

new Promise(function(resolve, reject) {
    reject(2);
}).then(null, function(err) {
    return err * 2;
}).then(onFulfilled, onRejected);
// やはり onFulfilled が 4 を取得、onRejected ではない

P.S.実際には [[PromiseValue]]=undefined, [[PromiseStatus]]=pending、但仍将按照上述状態で実行

###3.Promise.prototype.catch(onRejected) キャッチ

then(null, onRejected) と等価で、Promise オブジェクトを返し、簡単に以下のように理解:

  1. onReject がトリガーされた場合、返されるオブジェクトの [[PromiseValue]]=onReject の戻り値、[[PromiseStatus]]=resolved

  2. onReject が始終トリガーされない場合、catch を見ないふりして、直接元の Promise オブジェクトを返す

    var p = new Promise(function(resolve, reject) { resolve(2); }).catch(function() { console.log('onRejected'); return false; }); p.then(onFulfilled, onRejected); // onFulfilled が 2 を取得、catch は何の影響もない

P.S.実際には [[PromiseValue]]=undefined, [[PromiseStatus]]=pending、但仍将按照上述状態で実行

###4.Promise.all(iterable)

Promise オブジェクトを返し、iterable 中のすべての promiseresolve 後、onFulfilled をトリガーし、最初の否定 promise に遭遇すると即時終了し、onRejected をトリガー

//1.全肯定集合、onFulfilled をトリガー、渡されるパラメータは集合中のすべての promise の [[PromiseValue]] で構成された配列    
Promise.all([getPromise(), getPromise()]).then(onFulfilled, onRejected);

//2.全否定集合、最初の否定 promise に遭遇すると終了し、onRejected をトリガー、渡されるパラメータは該 promise の [[PromiseValue]]
// Promise.all([getPromise(true), getPromise(true)]).then(onFulfilled, onRejected);

// 3.一般集合、同上
// Promise.all([getPromise(true), getPromise()]).then(onFulfilled, onRejected);

P.S.all、すべて正常に完了するか、エラーで中断し、すべて停止

###5.Promise.race(iterable) 競争

Promise オブジェクトを返し、iterable 中に任意の 1 つの promiseresolve されると即時終了し、onFulfilled をトリガーし、否定 promise に遭遇すると即時終了し、onRejected をトリガー

// 1.全肯定集合、最初の肯定 promise に遭遇すると終了し、onFulfilled をトリガー、渡されるパラメータは該 promise の [[PromiseValue]]
// Promise.race([getPromise(), getPromise()]).then(onFulfilled, onRejected);
// 2.全否定集合、最初の否定 promise に遭遇すると終了し、onRejected をトリガー、渡されるパラメータは該 promise の [[PromiseValue]]
// Promise.race([getPromise(true), getPromise(true)]).then(onFulfilled, onRejected);
// 3.一般集合、同上
// Promise.race([getPromise(true), getPromise()]).then(onFulfilled, onRejected);

P.S.「競争」、結果が肯定か否定かに関わらず、最も速いもののみ

###6.Promise.resolve(value) 解決

1.valuepromise の場合、promise 中の resolve/reject のパラメータを対応する onFulfilled/onRejected に渡す

Promise.resolve(getPromise()).then(onFulfilled, onRejected);
// onFulfilled が resolve のパラメータを取得
Promise.resolve(getPromise(true)).then(onFulfilled, onRejected);
// onRejected が reject のパラメータを取得

2.value が thenable オブジェクト(then 属性を持つオブジェクト)の場合、obj を Promise オブジェクトにラップし、該オブジェクトの thenobj.then

Promise.resolve({
    then: function(onFulfilled, onRejected) {
        // onFulfilled(1);
        onRejected(new Error('obj.then err'));

        console.log('obj.then');
    }
}).then(onFulfilled, onRejected);

3.value が!thenable 値の場合、該値を Promise オブジェクトにラップし、該オブジェクトの [[PromiseValue]]=value, [[PromiseStatus]]=resolved、そのため then メソッドを呼び出す時、onFulfilled はこの値を受け取る

Promise.resolve(1).then(onFulfilled, onRejected);
// onFulfilled が 1 を取得

###7.Promise.reject(reason) 拒否

reason を Promise オブジェクトにラップし、該オブジェクトの [[PromiseValue]]=reason, [[PromiseStatus]]=rejected、そのため then メソッドを呼び出す時、onRejected はこの値を受け取る

注意:ここには thenable/!thenable の区別はなく、一律に扱う:

// 1.!thenable onRejected は Error オブジェクトを取得
Promise.reject(new Error('static reject')).then(onFulfilled, onRejected);
// 2.promise onRejected は包まれた肯定 Promise オブジェクトを取得
// 該オブジェクトの [[PromiseValue]]=promise, [[PromiseStatus]]=rejected
Promise.reject(getPromise()).then(onFulfilled, onRejected);
// 3.thenable onRejected は {then: xxx} を取得
Promise.reject({
    then: function(onFulfilled, onRejected) {
        console.log('obj.then');
    }
}).then(onFulfilled, onRejected);

完全なテスト DEMO:promise 实例

六.高度な使い方

  • ジェネレーター(generator)と連携

  • async/await と連携

(篇幅の制限により、後で説明)

参考資料

コメント

コメントはまだありません

コメントを書く