Promise

いつ完了するか分からない処理を順番に実行するのに使います。例えば、

  • API のレスポンスが返ってきた時
  • 指定秒スリープした時
  • DOM が出来上がった時

など。そして、そのような処理を行えるようにPromiseクラスがあります。
Promiseインスタンスはresolverejectコールバック関数を引数として受け取れる関数を渡して作ります。 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 のエラー処理
  });