非同期処理を一気に投げて全部成功したら続行する必要があり、Promise.all() 使ってみた。

なお、「一つでも成功したら続行」は Promise.rase() らしいので留意。

サンプルコード

/**
 * ランダムに1000~3000msくらい待ってresolve
 * ただし100回に15回くらいreject
 */
const unstableRandomTimeout = (id) => {
  const ms = 1000 + Math.random() * 2000;
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Math.random() < 0.15) {
        console.log('reject', id);
        reject(id);
      } else {
        console.log('resolve', id);
        resolve(id);
      }
    }, ms);
  });
};

// 実行
const promises = [
  unstableRandomTimeout(1),
  unstableRandomTimeout(2),
  unstableRandomTimeout(3),
  unstableRandomTimeout(4),
  unstableRandomTimeout(5)
];
Promise.all(promises)
  .then(values => {
    console.log('[success]', values);
  })
  .catch(err => {
    console.error('[failure]', err);
  });

全て成功した場合

処理が全て成功した場合、出力は以下のようになる。

resolve 3
resolve 4
resolve 2
resolve 1
resolve 5
success [ 1, 2, 3, 4, 5 ]

完了時点で .then() のコールバックが実行されている。values には実際に処理が成功した順番に関わらず、Promise.all() に渡した配列と同じ順番で結果が格納されている。

1つ失敗した場合

処理の中に失敗したものが1つある場合、出力は以下のようになる。

resolve 2
resolve 3
reject 4
[failure] 4
resolve 5
resolve 1

失敗した時点で .catch() のコールバックが実行され、err には失敗した処理の結果だけが渡ってくる。処理自体は全て実行されているが、.then() のコールバックは実行されない。

後述するが、.catch() のコールバックは一つでも失敗した時点で実行される。この時、他に同時進行中の処理が全て終わっているとは限らない。

2つ以上失敗した場合

さらに、処理の中に失敗したものが複数ある場合、出力は以下のようになる。

resolve 3
reject 1
[failure] 1
resolve 5
resolve 2
reject 4

これを見ると、.catch() のコールバックが実行されるのは最初の失敗だけということが分かる。

await Promise.all()

上記のサンプルコードは、以下のように書いても同じように動く。

// await は async 関数の中でしか使えないのでこう書いてます
(async () => {
  const promises = [
    unstableRandomTimeout(1),
    unstableRandomTimeout(2),
    unstableRandomTimeout(3),
    unstableRandomTimeout(4),
    unstableRandomTimeout(5)
  ];
  try {
    const values = await Promise.all(promises);
    console.log('[success]', values);
  }
  catch (err) {
    console.error('[failure]', err);
  }
})();

Promise.all() 自体が Promise を返すので、async/await できる。

このコードは、一つでも失敗した時点で catch ブロックに進むので注意。その時、同時進行中の他の処理は終わってないかもしれない。

もし「成功しようが失敗しようが同時進行する処理が全部終わるまで待ちたい」場合、単純な話、Promise 内で reject せずに全て resolve すればよい。それをするとエラーを投げなくなるので、try-catch も要らなくなる。