いくらPromiseの解説を読んでもよく分からないので、自分で試しながら考えます。
実行のタイミング
Promiseオブジェクトを作って返す関数で実験します。解説記事の多くは、new Promise
に渡す関数(executor)でsetTimeoutを使っていますが、ここでは使わないのがキモです。
function makePromise(num) {
return new Promise((resolve, reject) => {
if(num % 2 == 0)
resolve('OK');
else
reject('NG');
console.log('resolve or reject called');
});
}
makePromise(2)
としてresolve('OK')
を試します。
makePromise(2).then((x) => {
console.log(`then callback: ${x}`);
}).catch((r) => {
console.log(`catch callback: ${r}`);
});
console.log('then and catch called');
resolve or reject called
then and catch called
then callback: OK
executorは即座に実行されます。setTimeoutを使わなくてもthenに渡した関数は遅れて実行されることがわかります。
makePromise(1)
としてreject('NG')
を試しても同様です。catchに渡した関数は遅れて実行されます。
resolve or reject called
then and catch called
catch callback: NG
executorは即座に実行され、thenやcatchに渡した関数は非同期で実行されます。executor内で呼び出すresolveとrejectは、thenやcatchに渡す関数とは別物ということです。ここがわかりにくい。
resolveとthenは何をしているのか
resolveとthenのどちらを先に実行しても、thenに渡した関数は実行されます。上記の例は、resolveのほうが先に呼ばれています。次の例は、setTimeoutを使ってthenのあとでresolveを呼びます。
function makePromise(num) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if(num % 2 == 0)
resolve('OK');
else
reject('NG');
console.log('resolve or reject called');
}, 2000);
});
}
then and catch called
resolve or reject called
then callback: OK
Promiseの仕組みは次のようになっていると思われます。なお、「履行」「棄却」はfulfilled / rejectedの訳です。
- executor内でresolveを呼び出すと、Promiseオブジェクトのステータスが「保留中」から「履行済み」となり、「履行済み結果の値」にresolveの引数(上の例では
"OK"
)がセットされる。resolveはステータスを変えるもので、thenの関数を実行するわけではない。 - thenに関数を渡すと、履行済みのときに実行されるコールバック関数として登録される。thenは関数を登録するもので、実行するわけではない。
- コールバック関数を実行するのは、JavaScriptの実行環境。JavaScriptは定期的にPromiseオブジェクト一覧をチェックし、履行済みのものにコールバック関数が登録されていれば実行する。コールバック関数の引数は、resolveに渡した「履行済み結果の値」となる。
上記の説明は、resolveをreject、「履行済み」を「棄却済み」、thenをcatchとしてもたぶんだいたい同じ。
そのほかに
executor内でresolveやrejectは何度でも呼べますが、効果があるのは最初の一回だけです。
function makePromise(num) {
return new Promise((resolve, reject) => {
if(num % 2 == 0) {
resolve('OK1');
resolve('OK2');
}
else
reject('NG');
console.log('resolve or reject called');
});
}
resolve or reject called
then and catch called
then callback: OK1
executor内でresolveもrejectも呼ばないと、Promiseオブジェクトはずっと保留中になります。
function makePromise(num) {
return new Promise((resolve, reject) => {
console.log('do nothing');
});
}
do nothing
then and catch called
続く。