[JavaScript/TypeScript] Promise.allで全てのErrorをcatchするには?

 Promiseを並列実行したいときPromise.all使うことあると思いますが、Promise.allでは全てのErrorをcatchできません。

 今回はPromise.allの挙動とそこを解消する方法についてまとめていきます。

Promise.allの挙動について


 Promise.allはPromiseのリストを並列実行できます。また、Promise.all自身もPromiseを返却します。

 全てのPromiseがresolveであればPromise.allもresolveされ、1つでもrejectがあるとPromise.allはその時点でrejectされるという処理の流れです。

全てresolveの場合のPromise.allの挙動

 まずは全てresolveの場合のサンプルコード。この例では[1,2,3]が表示されます。

const p1 = new Promise((resolve, reject) => {
  resolve(1);
});

const p2 = new Promise((resolve, reject) => {
  resolve(2);
});

const p3 = new Promise((resolve, reject) => {
  resolve(3);
});

Promise.all([p1, p2, p3])
  .then((values) => {
    console.log(values);
  })
  .catch((e) => {
    console.log(e);
  });

rejectした場合のPromise.allの挙動

 次にp2とp3がrejectした場合。

 この場合はPromise.allのcatchブロックに入ります。しかし、p2のrejectでcatchに入り処理をして終了するため、p3のエラーは無視されてしまいます。

const p1 = new Promise((resolve, reject) => {
  resolve(1);
});

const p2 = new Promise((resolve, reject) => {
  reject("error2");
});

const p3 = new Promise((resolve, reject) => {
  reject("error3");
});

Promise.all([p1, p2, p3])
  .then((values) => {
    console.log(values);
  })
  .catch((e) => {
    console.log(e);
  });

どうやって全てのErrorをcatchするか?


 Promise.allではすべてをcatchすることはできません。

 Promise.allをラッピングすることでcatchはできないけどErrorを捉えられるようにします。

 このようにラッピングします。

 エラーが起きた場合でもresolveにしてしまい、resolveの引数には正常値、エラーをまとめたリストを入れるようにします。

 2つめの値がnullじゃなければエラー発生ととらえることができます。

const execParallelPromise = (promiseList: Promise<any>[]): Promise<any> => {
  return Promise.all(
    promiseList.map((p) => {
      return new Promise((resolve, reject) => {
        p.then((value) => {
            resolve([value, null]);
          })
          .catch((err) => {
            resolve([null, err]);
          });
      });
    })
  );
};

 先ほどの複数がrejectされる例で使ってみましょう。

 [[1, null], [null, “error2”], [null, “error3”]]という値が表示されます。

 1つ目が正常値、2つ目がエラーになっていることが分かると思います。

const p1 = new Promise((resolve, reject) => {
  resolve(1);
});

const p2 = new Promise((resolve, reject) => {
  reject("error2");
});

const p3 = new Promise((resolve, reject) => {
  reject("error3");
});

execParallelPromise([p1, p2, p3]).then((values) => {
  console.log(values);
});

欠点


 このやり方だとanyで型推論が効かないため型の恩恵が消えてしまいます。execParallelPromiseは色々anyにしないと動かないので仕方ないですが…。

 この場合に戻り値に型を指定するには以下のやり方になります。微妙な感じですが…。

parallelPromise([p1, p2, p3]).then((values) => {
  console.log(values);
  const [result, error]: [number | null, string | null] = values[0];
  console.log(result, error);
});

コメントを残す

メールアドレスが公開されることはありません。