Promise.allWait:一部のPromiseが失敗しても全てのPromiseが終わるまで待つ


stackoverflow記事Wait until all ES6 promises complete, even rejected promisesと同じニーズあった、Promise.allで一部のPromiseが失敗しても全てのPromiseが終わるまで待って欲しい、失敗した場合は最後に纏めて失敗理由の配列をrejectで返させたい。
本文はそれらの調査結果と対策をメモする。

Promise.all

Promise.allの仕様は一部のPromiseが失敗したら、他のPromiseの結果を待たずに直ちにrejectedされます。普通はそれで十分だが、ある時、やっぱり全て終わってから何かをするという時に、Promise.allだけでは無理。

Promise.allSettled

2019.9.12現在また仕様策定中(Stag4 Draft)のPromise.allSettledは全て終わったかどうかの意味であるため、結果は全て処理成功resolvedになる。その結果Objectのstatusによって該当Promiseが成功か失敗かを判別する。
自分が欲しいのは、失敗した場合もちゃんとrejectedを返すことので、それでも行けない。

独自の実装

やはり独自で実装しないといけなさそう。以下1つの例。


/**
 * Promise.allと同じように渡された全てのPromiseを非同期で処理する。
 * 一部のPromiseがrejectedになっても全てのPromiseの結果が出るまで待つ。
 *
 * @returns 全てのPromiseがresolvedの場合、Promise.allと同じでPromise.resolve(results)を返す
 *          一部のPromiseがrejectedの場合、Promise.reject(reasons)を返す
 */
async function promiseAllWait(promises) {
  // 失敗フラグを立てる
  const wrappedPromises = promises.map((promise) => {
    return promise.then((value) => {
      return {
        isFailed: false,
        value,
      };
    }).catch((reason) => {
      return {
        isFailed: true,
        reason,
      };
    });
  });

  // 非同期処理をする
  const results = await Promise.all(wrappedPromises);

  // 結果を分ける
  const [values, reasons] = results.reduce(([valueArray, reasonArray], result) => {
    if (result.isFailed) {
      reasonArray.push(result.reason);
    } else {
      valueArray.push(result.value);
    }
    return [valueArray, reasonArray];
  }, [[], []]);

  // rejectedの場合
  if (reasons.length) {
    return Promise.reject(reasons);
  }

  // resolvedの場合
  return Promise.resolve(values);
}

promiseAllWaitを使えば思った通りのフローでプログラミングできる。


const promises = [p1, p2, p3];
promiseAllWait(promises).then((values) => {
  // 全てが成功した場合の処理
}).catch((reasons) => {
  // 全てが処理完了、一部が失敗した場合の処理
});

参考情報