前回のまとめ
subscribersが登録されていないとpromiseには何も起こらない
いつsubscribersが登録されるのか?
それはthen
then
export default function then(onFulfillment, onRejection) {
const parent = this;
const child = new this.constructor(noop);
if (child[PROMISE_ID] === undefined) {
makePromise(child);
}
const { _state } = parent;
if (_state) {
const callback = arguments[_state - 1];
asap(() => invokeCallback(_state, child, callback, parent._result));
} else {
subscribe(parent, child, onFulfillment, onRejection);
}
return child;
}
新しいPromiseが作られる。
呼び出し元から_state
を取り出し、_state !== PENDING
かどうかで処理が分かれる。
この時点で_state === PENDING
である場合
初期化時にresolverを渡さない場合は_state
が変化しないのでPENDINGのままthen
が呼ばれる。
このときsubscribe(parent, child, onFulfillment, onRejection)
が実行される。
function subscribe(parent, child, onFulfillment, onRejection) {
let { _subscribers } = parent;
let { length } = _subscribers;
parent._onerror = null;
_subscribers[length] = child;
_subscribers[length + FULFILLED] = onFulfillment;
_subscribers[length + REJECTED] = onRejection;
if (length === 0 && parent._state) {
asap(publish, parent);
}
}
ここで_subscribers
に登録される。
1度のsubscribeでchild
, fulfill
, reject
が登録される。
resolverがない場合はparent._state === PENDING
なので何も実行されない。
ここまででresolverがない状態でPromiseを初期化するのは意味がないことがわかった。
つまり、初期化時は_subscribers
は登録されないままで良い。
_state !== PENDING
である場合
resolverがある場合かつresolveが呼ばれた場合はここで_state === FULFILLED
なので、callback = onFulfillment
となり、asap(() => invokeCallback(_state, child, callback, parent._result))
が実行される。
asapはqueueのスケジューリングなのでここでは無視していく。invokeCallback
が重要。
function invokeCallback(settled, promise, callback, detail) {
let hasCallback = isFunction(callback),
value, error, succeeded, failed;
if (hasCallback) {
value = tryCatch(callback, detail);
if (value === TRY_CATCH_ERROR) {
failed = true;
error = value.error;
value = null;
} else {
succeeded = true;
}
if (promise === value) {
reject(promise, cannotReturnOwn());
return;
}
} else {
value = detail;
succeeded = true;
}
if (promise._state !== PENDING) {
// noop
} else if (hasCallback && succeeded) {
resolve(promise, value);
} else if (failed) {
reject(promise, error);
} else if (settled === FULFILLED) {
fulfill(promise, value);
} else if (settled === REJECTED) {
reject(promise, value);
}
}
callback
が関数ではない場合は、引き続きchildに対してfulfill
が呼ばれて無限ループになり何も起こらない・・・
callback
が関数の場合は、callback(detail)
がtry-catch内で実行される。
実行結果がchildのPromiseに渡され処理がつづく。
次回
resolveにobjectまたはfunctionを渡す場合の処理を読んでいく