Promiseのメモ - その1: resolveとthen の続き。
チェーン
thenやcatchをいくつもつないで、「1つめの処理→2つめの処理」のような流れを作ることをプロミスチェーンと言います。
次の例は、2つめのthenを追加したものです。1つめのthenのコールバック関数で戻り値を返すと、その戻り値が2つめのthenのコールバック関数の引数になります。
function makePromise(num) {
return new Promise((resolve, reject) => {
if(num % 2 == 0)
resolve('OK');
else
reject('NG');
});
}
makePromise(2).then((x) => {
console.log(`then1 callback: ${x}`);
return x + x;
}).then((x) => {
console.log(`then2 callback: ${x}`);
}).catch((r) => {
console.log(`catch callback: ${r}`);
});
then1 callback: OK
then2 callback: OKOK
makePromise(1)
としてrejectすると、thenは2つともスキップされて、catchに渡した関数が実行されるように見えます。
catch callback: NG
thenとcatchが返すPromise
MDNの説明でthenの仕様をチェックします。
The then() method returns a Promise.
thenはPromiseオブジェクトを返します。はっきり書いていませんが、新しいPromiseオブジェクトを作って返すということです。
- returns a value, the promise returned by then gets resolved with the returned value as its value;
- doesn't return anything, the promise returned by then gets resolved with an undefined value;
コールバック関数が実行されると、新しいPromiseオブジェクトは履行済みになり、履行済み結果の値はコールバック関数の戻り値になります。戻り値がないときは、履行済み結果の値はundefinedになります。
次のようにチェーンをばらしてthenが返すPromiseを調べます。また、コールバック関数が実行されたあとの状態も調べます。
let promise = makePromise(2);
console.log(promise);
let promise_then1 = promise.then((x) => {
console.log(`then1 callback: ${x}`);
return x + x;
});
console.log(promise_then1);
let promise_then2 = promise_then1.then((x) => {
console.log(`then2 callback: ${x}`);
});
console.log(promise_then2);
let promise_catch = promise_then2.catch((r) => {
console.log(`catch callback: ${r}`);
});
console.log(promise_catch);
setTimeout(() => {
console.log(promise);
console.log(promise_then1);
console.log(promise_then2);
console.log(promise_catch);
}, 1000);
Promise {<resolved>: "OK"}
Promise {<pending>}
Promise {<pending>}
Promise {<pending>}
then1 callback: OK
then2 callback: OKOK
Promise {<resolved>: "OK"}
Promise {<resolved>: "OKOK"}
Promise {<resolved>: undefined}
Promise {<resolved>: undefined}
makePromise関数が1つ、thenが2つ、catchが1つのPromiseオブジェクトを作っています。thenとcatchが作ったPromiseは非同期で自動的に保留中から履行済みに変わっています。
makePromise(1)
としてrejectすると、次の結果になります。
Promise {<rejected>: "NG"}
Promise {<pending>}
Promise {<pending>}
Promise {<pending>}
catch callback: NG
Promise {<rejected>: "NG"}
Promise {<rejected>: "NG"}
Promise {<rejected>: "NG"}
Promise {<resolved>: undefined}
thenやcatchはスキップされるわけでなく、「thenで登録したコールバック関数は棄却済みのときは実行されない」「catchで登録したコールバック関数は履行済みのときは実行されない」ということです。実行されないときは、履行/棄却済み結果の値は次のPromiseオブジェクトに引き継がれます。
catchもPromiseを返すので、catchのうしろにthenをつなげることができます。
makePromise(2).then((x) => {
console.log(`then1 callback: ${x}`);
return x + x;
}).catch((r) => {
console.log(`catch callback: ${r}`);
return r + r;
}).then((x) => {
console.log(`then2 callback: ${x}`);
});
then1 callback: OK
then2 callback: OKOK
この例はmakePromise(1)
とすると、then2 callback: NGNG
となります。
例外
rejectだけでなく、一般的な例外が発生したときも、catchで登録したコールバック関数が呼ばれます。コールバック関数の引数にはErrorオブジェクトが入ります。
function makePromise(num) {
return new Promise((resolve, reject) => {
if(num % 2 == 0)
resolve('OK');
else
foo.bar();
});
}
makePromise(1).then((x) => {
console.log(`then1 callback: ${x}`);
}).catch((r) => {
console.log(`catch callback: ${r}`);
});
catch callback: ReferenceError: foo is not defined
コールバック関数で例外が発生したときも、次のcatchのコールバック関数が呼ばれます。
function makePromise(num) {
return new Promise((resolve, reject) => {
if(num % 2 == 0)
resolve('OK');
else
reject('NG');
});
}
makePromise(2).then((x) => {
console.log(`then1 callback: ${x}`);
foo.bar();
}).catch((r) => {
console.log(`catch callback: ${r}`);
});
then1 callback: OK
catch callback: ReferenceError: foo is not defined
catchがない場合
executorでrejectを呼んだ場合や、executorやコールバック関数で例外が発生した場合に、catchがないと例外が発生します(nodeではUnhandledPromiseRejectionWarning
が出ます)。
function makePromise(num) {
return new Promise((resolve, reject) => {
if(num % 2 == 0)
resolve('OK');
else
reject('NG');
});
}
makePromise(1).then((x) => {
console.log(`then1 callback: ${x}`);
});
Uncaught (in promise) NG
Promiseで発生した例外をtry - catchで捕まえることはできません(asyncとawaitを使えばできます)。次の例は意味なしです。
try {
makePromise(1).then((x) => {
console.log(`then1 callback: ${x}`);
});
}
catch(e) {
console.log(e);
}
ここまでの感想
Promiseのノリは、Promiseオブジェクトをじゃんじゃん作って、使わないものはほっておく、というものです。割とゴージャスなシステムと言えます。