Promiseを理解する
Promiseは将来的に評価可能となる可能性のある、タグ付き(成功あるいは失敗)の値とそれを用いた処理を表す型であるということができる
Promise オブジェクトは非同期処理の最終的な完了処理 (もしくは失敗) およびその結果の値を表現します。
これと同じことを言ってる
どのような実装かは実装に任せられているしユーザーからは不可視ではあるが内部にthenを用いて追加した関数とそのメタデータを保持している
catchとfinallyも仕様的にはthenを呼び出すのとほぼ同等の形で記述されている
Promiseコンストラクタの第一引数は同期的に呼ばれる
thenは新しいPromiseを返す
thenの引数が同期的に呼ばれることはない
すべてmicrotaskとして非同期的に呼ばれる
thenを完了済みのPromiseに対して呼び出してもコールバックは概ね呼ばれる
概ねなのはUnhandledPromiseRejectionのタイミングがちょっと早すぎるのでtig.icon
評価可能となる可能性のあるというのはfetchで延々サーバがレスポンスを返してこない場合永遠にpendingであるといったシチュエーションを考えている
評価可能となるという表現を使ったのは評価しなくても問題はないため
エラーの方の値は評価しないと怒られるけど
用語(一般向け)
fulfilled
Promise pに対してp.then(f,r)を呼び出したときfをHandlerとするJobがJobキューに即座に(同期的に?)enqueueされる状態をfulfilledと呼ぶ
HTML仕様だと多分microtaskqueueにmicrotaskを突っ込むってことになると思う
rejected
Promise pに対してp.then(f,r)を呼び出したときrをHandlerとするJobがJobキューに即座(同期的に?)にenqueueされる状態をrejectedと呼ぶ
pending
fullfilledでもrejectedでもないものをpendingと呼ぶ
settled
pendingでないものをsettledと呼ぶ
すなわちrejectedまたはfullfilledであるものであるということである
resolved
settledなPromiseはresolvedである
加えて別のPromiseに"locked in"されたもの、すなわち別のPromiseに処理が移譲されたものもresolvedである
この状態の時にPromiseをresolve、あるいはrejectしようとしても効果はない
無視される
unresolved
resolvedではないPromise
この状態のPromiseはすべてpendingである
用語(一般向けでない)
PromiseFulfillReactions
Promiseがrejectされた際に呼ばれる処理をあらわす
Listだったりundefinedだったりする
ECMA仕様より
PromiseRejectReactions
Promiseがrejectされた際に呼ばれる処理をあらわす
Listだったりundefinedだったりする
ECMA仕様より
Promiseの作り方
Promiseコンストラクター
Promiseは引数として関数をただ一つ取る
これをECMAScriptの仕様にならってexecutorと呼ぶことにする
executorは2つの引数を与えられて同期的に呼び出される
第一引数をresolve、第二引数をrejectと呼ぶことにする
これは慣例的なものである
resolveにthenが呼び出し可能でない値を第一引数として与えた場合Promiseはその値で解決される
resolveはthenの第一引数として追加されたもの全てについてJobを作成してJobキューにenqueueする
PromiseFulfillReactionsの各エントリについてJobを作成してJobキューにenqueueすると言いかえることができるというか仕様にはそう書いてある
JobキューはFIFOである
呼び出し可能である場合はJobキューに現在のPromiseのresolveとrejctを引数としたthenの呼び出し(thenable.then(resolve,reject))が追加される
厳密ではないけどまぁいいはず
これがlocked in
rejectを呼んだ場合はPromiseはrejected状態に遷移し、rejectの第一引数で解決される。
rejectはthenの第二引数として追加されたもの全てについてJobを作成してJobキューにenqueueする
PromiseRejectReactionsの各エントリについてJobキューにJobを作成してenqueueすると言いかえることができるというか仕様にはそう書いてある
settledになった時点でPromiseFulfillReactionsとPromiseRejectReactionsはundefinedとなる
Jobの作成についてはもう少し下に記載
先にthenをやったほうが良さそうなので
p.then(f,r)を呼び出したときに起こること
pがpending
fについて
呼び出し可能ならばHandlerをfとしてPromiseFulfillReactionsに末尾に追加する
そうでないならHandlerをemptyとしてPromiseFulfillReactionsの末尾に追加する
rについて
呼び出し可能ならばHandlerをfとしてPromiseRejectReactionsに末尾に追加する
そうでないならHandlerをemptyとしてPromiseRejectReactionsの末尾に追加する
pがfulfilledまたはrejected
適当にJobを作成してJobキューにenqueueする
すべての状態で後続のPromiseが返却される
この後続のPromiseはJobの実行中に解決される
Jobの作成
以下のような処理を実行するJobを作成する
存在するならReactionのHandlerを呼び出す
後続のPromiseの適した処理(resolve or reject)をHandlerの結果または現在の状態によって呼び出す
おまけ
p.catch(r)
p.then(undefined,r)を呼んでるだけ
p.finally(onFinally)
code: js
p.then(
x => Promise.resolve(onFinally()).then(() => x),
err => Promise.resolve(onFinally()).then(() => {throw err;})
);
とほぼ同じ
厳密にはPromise.resolveの部分が異なる
継承時の振る舞いが考慮されている
補足: finally コールバック内で throw が行われた場合 (または、拒否されたプロミスを返した場合)、 throw を呼び出すときに指定された拒否理由と共に新しいプロミスが拒否されます。
MDNより
上のコードを見ればまぁわかるね
Promise.resolve(v)
new Promise(resolve=>resolve(v))
よく見てないけど多分…
Promise.reject(err)
new Promise((_resolve,reject)=>reject(err))
よく見てないけど多分…
Promise.all(iterable)
code:js
new Promise((resolve,reject) => {
const results = [];
let remaining=0;
--remaining;
if(remaining === 0){
resolve(results);
}
});
for (const entry of iterable){
++remaining;
}
if(remaining === 0){
resolve(results);
}
});
よく見てないけど多分こんな感じ…
Promise.race(iterable)
code: js
new Promise((resolve,reject) => {
for(const entry of iterable){
Promise.resolve(entry).then(resolve,reject):
}
})
Promise.any(iterable)
Promise.allのエラー版
Promise.allSettled
参考