Promiseを理解する
Promiseは将来的に評価可能となる可能性のある、タグ付き(成功あるいは失敗)の値とそれを用いた処理を表す型であるということができる
> Promise オブジェクトは非同期処理の最終的な完了処理 (もしくは失敗) およびその結果の値を表現します。
これと同じことを言ってる
どのような実装かは実装に任せられているしユーザーからは不可視ではあるが内部に then
を用いて追加した関数とそのメタデータを保持している
catch
と finally
も仕様的には then
を呼び出すのとほぼ同等の形で記述されている
Promiseコンストラクタの第一引数は同期的に呼ばれる
then
は新しいPromiseを返す
then
の引数が同期的に呼ばれることはない
すべてmicrotaskとして非同期的に呼ばれる
then
を完了済みのPromiseに対して呼び出してもコールバックは概ね呼ばれる
概ねなのはUnhandledPromiseRejectionのタイミングがちょっと早すぎるので

評価可能となる可能性のあるというのは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)
jsp.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)
jsnew Promise((resolve,reject) => {
const results = [];
let remaining=0;
const wrapResolve = ([idx,result])=> {
results[idx] = result;
--remaining;
if(remaining === 0){
resolve(results);
}
});
for (const entry of iterable){
Promise.resolve(entry).then(result => wrapResolve([remaining,result]),reject);
++remaining;
}
if(remaining === 0){
resolve(results);
}
});
よく見てないけど多分こんな感じ…
Promise.race(iterable)
jsnew Promise((resolve,reject) => {
for(const entry of iterable){
Promise.resolve(entry).then(resolve,reject):
}
})
Promise.any(iterable)
Promise.all
のエラー版
Promise.allSettled
参考