いつ完了するか分からない処理を順番に実行するのに使います。例えば、
API のレスポンスが返ってきた時
指定秒スリープした時
DOM が出来上がった時
など。そして、そのような処理を行えるようにPromise
クラスがあります。Promise
インスタンスはresolve
とreject
コールバック関数を引数として受け取れる関数を渡して作ります。 resolve
は処理が成功した時に呼ぶもので、reject
は処理が失敗した時に呼ぶものです。
インスタンスメソッド
Promise#then
resolve
コールバック関数が呼ばれるとPromise#then
メソッドが呼ばれます。このメソッドはresolve(arg)
が実行された直後に実行されます。resolve
に渡した第一引数がPromise#then
の第一引数の値になります。
const processing = new Promise(resolve => {
resolve({value: 1});
});
processing.then(obj => {
console.log(obj); // {value: 1}
});
Promise#catch
reject(arg)
が実行された時に呼ばれるのがPromise#catch
です。
const processing = new Promise((_, reject) => {
reject({value: 1});
});
processing.catch(obj => {
console.log(obj); // {value: 1}
});
実はthrow
でも同じようにPromise#catch
が呼ばれます。
const processing = new Promise((_, reject) => {
throw {value: 1};
});
processing.catch(obj => {
console.log(obj); // {value: 1}
});
注意する点として、catch
できるのは「reject
またはthrow
された非同期関数で終わっている場合」に限ります。なので、以下のように何も返さないPromise#catch
などで握り潰してしまうと、その部分のキャッチした値は完全に捨ててしまうことになります。
const processing = new Promise((_, reject) => {
throw {value: 1};
})
.catch(() => undefined);
processing.catch(obj => {
// 呼ばれない
console.log(obj); // {value: 1}
});
連続した処理
最初に書いたとおりこれの使う理由には処理を連続で書けるようにする点があります。では、どうするのがいいかという事の個人的な話です。
まず、常にPromise#catch
をチェーン毎に1つにします。
処理を連続で書けると言っても、throw
するかもしれない処理をそのまま渡すと、Promise#catch
が難しくなっていきやすいと感じます。それはPromise#catch
からPromise#catch
間や親との間の伝達の問題です。
ちゃんとエラー処理を施した非同期関数を別に作り渡すようにすると良いと思います。
別の非同期関数にしてチェインに渡すことで次の非同期関数には、自分自身のPromise#then
の結果だけを渡すことができ、同様に自分へはPromise#then
の結果が渡ってくるようにできます。つまり、自分自身のPromise#catch
が後のチェイン上の非同期関数へ影響を与えることがなくなります。
例えばこんな感じです。(new Promise(resolve => resolve())
は() => Promise.resolve()
、new Promise((_, reject) => reject())
は() => Promise.reject()
のように置き換えれます)
const childAsyncFn1 = () => Promise.reject("...");
const childAsyncFn2 = () => Promise.reject("...");
const childAsyncFn3 = () => Promise.reject("...");
const mainAsyncFn1 = () => {
return childAsyncFn1()
.then(childAsyncFn2);
};
const mainAsyncFn2 = () => {
return Promise.resolve()
.then(childAsyncFn3);
};
await mainAsyncFn1()
.then(mainAsyncFn2)
.catch(err => {
// childAsyncFn1 のエラー処理
});
そしてPromise#catch
ではできるだけPromise#then
と抽象的に同じ意味の値を返すようにすると良いと思います。最終手段のエラー処理と頭に置いておくと良いんじゃないかなと思います。
const childAsyncFn1 = () => Promise.reject("...");
const childAsyncFn2 = () => Promise.reject("...");
const childAsyncFn3 = () => Promise.reject("...");
const mainAsyncFn1 = () => {
return childAsyncFn1()
.then(childAsyncFn2)
.catch(() => {
// 後が問題なく動くような抽象的な値を返す
// これにより必ず `Promise#then` が返される
return {/*...*/}
});
};
const mainAsyncFn2 = something => {
return Promise.resolve(something)
.then(childAsyncFn3);
};
// mainAsyncFn2 へ渡るようになる
await mainAsyncFn1()
.then(mainAsyncFn2)
.catch(err => {
// もしかしたら childAsyncFn2 のエラー処理
});