Promise thenチェーンの落とし穴


一、thenチェーンにおいてPromiseオブジェクトの状態は、その前のPromiseオブジェクトの状態と直接関係がない.
すなわち、var promise2 = promise1.then(onFulfilled, onRejected)式におけるpromise2の状態はpromise1と直接関係がない.
//   promise1  
var promise1 = new Promise((resolve, reject) => {
    reject('Rejected')
})

//   promise2  ,  `promise1.then`    
var promise2 = promise1.then(value => {
    console.log(`[promise1]: fulfilled, value='${value}'`)
}, reason => {
    console.log(`[promise1]: rejected, reason='${reason}'`)
})

promise2.then(value => {
    console.log(`[promise2]: fulfilled, value='${value}'`)
}, reason => {
    console.log(`[promise2]: rejected, reason='${reason}'`)
})
出力:
[promise1]: rejected, reason='Rejected'
[promise2]: fulfilled, value='undefined'
promise 1はrejectedになりましたが、promise 2はfulfilled状態です.
1.2解析promise2 = promise1.then(onFulfilled, onRejected)2点を覚えてください:
  • promise1の最終状態は、onFulfilledまたはonFulfilledコールバック関数を決定するだけである.
  • promise2の最終状態はonFulfilled OR onRejectedのコールバック関数の影響を受けただけです.
  • onFulfilled/onRejectedのコールバック関数がありますか?
  • onFulfilled/onRejectedコールバック関数実行中に異常が発生しましたか?
  • onFulfilled/onRejectedコールバック関数の戻り値x.
  • 1.onFulfilled/onRejectedコールバック関数がない場合:
    プロミゼ1がfulfilled(rejected)であり、onFulfilled(onRejected)のコールバック関数がない場合、プロミゼ2はプロミゼ1の状態(すなわちpromise2の状態がpromise1の状態と一致する)を採用する.
    var promise1 = new Promise((resolve, reject) => {
        reject('Rejected')
    })
    // promise1    `onRejected`    
    var promise2 = promise1.then()
    
    promise2.then(value => {
        console.log(`[promise2]: fulfilled, value='${value}'`)
    }, reason => {
        console.log(`[promise2]: rejected, reason='${reason}'`)
    })
    出力:
    [promise2]: rejected, reason='Rejected'
    var promise1 = new Promise((resolve, reject) => {
        resolve('Fulfilled')
    })
    // promise1    `onFulfilled`    
    var promise2 = promise1.then()
    
    promise2.then(value => {
        console.log(`[promise2]: fulfilled, value='${value}'`)
    }, reason => {
        console.log(`[promise2]: rejected, reason='${reason}'`)
    })
    出力:
    [promise2]: fulfilled, value='Fulfilled'
    2.onFulfilled/onRejectedの実行中に異常が発生した場合:
    このときReject promise 2対象となり、異常をreasonとする.
    var promise1 = new Promise((resolve, reject) => {
        resolve('Fulfilled')
    })
    
    // promise1 `onFulfilled`         
    var promise2 = promise1.then(() => {
        throw new Error('A Error in onFulfilled func')
    })
    
    promise2.then(value => {
        console.log(`[promise2]: fulfilled, value='${value}'`)
    }, reason => {
        console.log(`[promise2]: rejected, reason='${reason}'`)
    })
    出力:
    [promise2]: rejected, reason='Error: A Error in onFulfilled func'
    3.OFulfilled/onRejeced関数の戻り値がxならば:onFulfilledonRejected関数が正常に実行された場合、戻り値はxである(指定された戻り値が表示されていない場合、xはundefinedである).
    3.1 xがPromiseオブジェクトである場合、promise2xの状態を採用する.
    var promise1 = new Promise((resolve, reject) => {
        resolve('Fulfilled')
    })
    
    // promise1 `onFulfilled`      `promise1`  
    var promise2 = promise1.then(() => {
        return promise1;
    })
    
    promise2.then(value => {
        console.log(`[promise2]: fulfilled, value='${value}'`)
    }, reason => {
        console.log(`[promise2]: rejected, reason='${reason}'`)
    })
    出力:
    [promise2]: fulfilled, value='Fulfilled
    このときpromise2promise1は同じ状態と同じvalueを有する.
    3.2もしxpromise2が等しいなら、TypeErrorの異常を投げる:
    前の規則が成立する前提はxpromise2が等しくないということです.xおよびpromise2が等しい場合は、TypeErrorが異常であり、promise2のオブジェクトrejectd状態であるリリースがスローされる.
    var promise1 = new Promise((resolve, reject) => {
        resolve('Fulfilled')
    })
    
    // promise1 `onFulfilled`       `promise2`
    var promise2 = promise1.then(() => {
        return promise2;
    })
    
    promise2.then(value => {
        console.log(`[promise2]: fulfilled, value='${value}'`)
    }, reason => {
        console.log(`[promise2]: rejected, reason='${reason}'`)
    })
    出力:
    [promise2]: rejected, reason='TypeError: Chaining cycle detected for promise #'
    3.3 xthenableオブジェクトである場合:
    大体次の流れを実行します.
    var then = x.then;
    then.call(x, resolve, reject);
    このプロセスはPromiseResolaveThe nable Jobとも呼ばれる.
  • は、x.thenの内部でresolve(y)を先に呼び出した場合、[[Resolive](promise,y)を実行する.この時、新たなPromise解析プロセスが開始されます.y万はxと同じではなく、無限再帰が発生します.
  • もしx.thenの内部にrejected(y)が先に呼び出されたら、promise2rejectedになり、yをreasonとする.
  • もしx.then内部に異常が先行したら、promise2rejectedになり、異常をreasonとする.
  • thenableオブジェクトは一般的ではないと思いますが、Promise配置関数の実参関数はthenableオブジェクトのthenメソッドルールに従います.
    var thenableA = {
        name: 'thenableA',
        then: function (resolve, reject) {
            console.log(`I'am ${this.name}`);
            resolve(this.name)
        }
    }
    
    new Promise((resolve, reject) => {
        console.log('create promise1');
        resolve(thenableA)
    }).then(() => {
        console.log('promise1 fulfilled');
    })
    
    // promise1 `onFulfilled`      thenableA
    new Promise(resolve => {
        console.log('create promise2');
        resolve();
    }).then(() => {
         console.log('promise2 fulfilled');
    })
    出力:
    create promise1
    create promise2
    I'am thenableA
    promise2 fulfilled
    promise1 fulfilled
    promise2 fulfilledより前に、PromiseResolaveThe nable Jobプロセスは、他のコンテキストコードの解析後に、then方法の解析が行われることを保証するために、jobとしてマイクロタスクキューに参加する必要があるからである.
    3.4その他の場合(promise1 fulfilledはPromiseオブジェクトでもxオブジェクトでもない):thenableはful filledになり、promise2をそのvalueとする.
  • は、冒頭のDEMOのようにxpromise1になっても、rejectedpromise2の状態である.
  • fulfilledpromise1コールバック関数の戻り値onRejectedxであるので、undefinedのvalueはpromise2である.
  • トランスポートundefinedを行うには、xの関数に表示されているリターンonFulfilledを表示しなければならない.
  • 二、xpromsie.finally(onFinally)と同等ではない.
    Promiseがどんな状態にあってもpromsie.then(onFinally, onFinally)コールバック関数をトリガするが、onFinallypromsie.finally(onFinally)と同等ではない.
    // Resolve
    var promise2 = Promise.resolve(2)
    .then(() => {});
    
    promise2.then( value => {
        console.log(`Fulfilled, value=${value}`) // value undefined
    })
    
    var promise2 = Promise.resolve(2)
    .finally( () => {});
    
    promise2.then( value => {
        console.log(`Fulfilled, value=${value}`) // value 2
    })
    
    // Reject
    var promise2 = Promise.reject(2)
    .then(() => {}, () => {})
    
    // promise2    fulfilled
    promise2 .then(value => {
        console.log(`Fulfilled, value=${value}`) // // value undefined
    }, reason => {
        console.log(`Rejected, reason=${reason}`)
    })
    
    var promise2 = Promise.reject(2).finally(() => {}, () => {});
    
    // promise2   rejected
    promise2 .then(value => {
        console.log(`Fulfilled, value=${value}`)
    }, reason => {
        console.log(`Rejected, reason=${reason}`) // reason 2
    })
  • promsie.then(onFinally, onFinally)コールバック関数にはパラメータがありません.Promiseの終状態がfulfilledなのかそれともrejectなのかが分かりません.
  • onFinally方法の原則は、finallyチェーンの実行プロセスを変更しないことである.(finally方法の内部に例外がない限り)、上記の例ではthen方法は前後のthenチェーンが存在しないように見える.
    Promise.reject('failed')
    .finally(() => {
        console.log('handle in finally')
        return 'Hi i am finally'; //       value,     Promise     rejected
    })
    .then(value => {
        console.log(`Fullfilled: handle in then value=${value}`)
    }, reason => {
        console.log(`Rejected: handle in then reason=${reason}`) // 
    })
  • は、finallyのコールバック関数がPromiseである場合にも、このPromiseオブジェクトが最終状態に入った後に、後のfinallyチェーンが実行される.
    Promise.reject('failed')
    .finally(() => {
        console.log('handle in finally')
        console.log('waiting 3s ...')
        return new Promise(resolve => { // 3s       
            setTimeout(resolve, 3000) //   fullfilled,  finally     Promise    rejected
        })
    })
    .then(value => {
        console.log(`Fullfilled: handle in then value=${value}`)
    }, reason => {
        console.log(`Rejected: handle in then reason=${reason}`)
    })
  • thenのコールバック関数が異常を投げた場合、またはfinallyのPromiseに戻った場合、前のPromiseの終状態valueまたはreason
    Promise.reject('failed')
    .finally(() => {
        console.log('handle in finally')
        console.log('waiting 3s ...')
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                reject('reason from finally callback ')
            }, 3000)
        })
    })
    .then(value => {
        console.log(`Fullfilled: handle in then value=${value}`)
    }, reason => {
        console.log(`Rejected: handle in then reason=${reason}`)
    })
    の出力を差し替えます.
    handle in finally
    waiting 3 s…
    Rejeced:handle in then reason=reason from finally calback
  • rejectedの行動は素晴らしいです.最後にやはりfinallyチェーンの最後に書いてください.コードの読み取り障害を防ぐために.
  • then方法は、原則として、前のPromiseオブジェクトの結果を伝達するが、エラーが発生したら、後のfinallyチェーンの最新のエラーを教えてくれる.
  • 2.2実現then
    Promise.prototype.finally = function(onFinally) {    
        return this.then(value => {
            return Promise.resolve(onFinally())
                .then(
                () => value, //     Promise     `value`
                reason => {
                    throw reason //     ,     `reason`
                });
            }, reason => {
               return Promise.resolve(onFinally())
                   .then(
                   () => {
                       throw reason //     Promise     `reason`
                   }, 
                   reason => {
                        throw reason //     ,     `reason`
                   });
            })
    }
    Promise.prototype.finally(onFinally)のコールバックは透過性の新しいonRejectedであるので、reasonのコールバック関数を省き、即ち、書き方を簡略化することができる.
    Promise.prototype.finally = function(onFinally) {    
        return this.then(value => {
            return Promise.resolve(onFinally())
                .then(() => value) //     Promise     `value`;
            }, reason => {
               return Promise.resolve(onFinally())
                   .then(
                   () => {
                       throw reason //     Promise     `reason`
                   });
            })
    }
    三、onRejectedは、Promise.resolve(value)と同等ではない.
    new Promise(resolve => {
        resolve();
    })
    .then(() => {
        console.log(1)
    })
    
    Promise.resolve()
    .then(() => {
        console.log(2)
    })
    出力結果:1->2、new Promise(resolve => resolve(value))をPromiseオブジェクトに変更してみます.
    var p = new Promise(resolve => {
        resolve();
    });
    
    new Promise(resolve => {
        resolve(p);
    })
    .then(() => {
        console.log(1) //           EventLoop
    })
    
    Promise.resolve(p)
    .then(() => {
        console.log(2)
    })
    出力結果が変わった.
  • Promise.resove(value)において、実参加valueがPromiseオブジェクトである場合(thenableオブジェクトを含まない)、直接に参加に戻る.
  • valueにおいて、new Promise(resolve => resolve(value))がPromiseまたはthenableオブジェクトである場合、PromiseResolaveThe nable Jobが生成され、非同期jobが増加する.
  • 参照
  • git Hubメモ:JS-ES 6-Promise/Promise A+仕様
  • から整理されています.