Skip to content

JavaScriptのArray.prototype.map()で非同期処理するときPromise.allは使うべきではない

📅 December 19, 2020

⏱️4 min read

どうも、寒くて寒くて震えるおじさんです。

JSの非同期処理をループやmap等で連続で行う場合、皆さん同処理しておりますでしょうか?

「js 非同期 map」なんかで調べると、Promise.all使おうぜ!!!っていう記事が多いので、意義を唱えようと思います。

結論

Promise.all だと、Promiseオブジェクトが1つでもrejectされると全体として、rejectされてしまうので、 Promise.allSettled を使うべき。

Promise.allSettledは現状IE非対応なので、IEへの対応が必要な場合は、polyfillなどで対応が必要)

ぐぐると出てくるコード

コードを見たほうが早いと思うので、実際にコードを見てみましょう。こんなやつ。funcPromise は実際はAPI叩くとか何かしらの時間がかかるPromiseオブジェクト返す処理だと思って下さい。問題なく、3回非同期処理を行って、処理の結果も取得できてますね。

const funcPromise = (result) => {
  return new Promise((resolve, reject) => {
    if (result) {
      resolve("ok")
      return
    }
    reject("error")
  })
}

(async () => {
  const array = [1,2,3];

  const res = await Promise.all(array.map( (v) => {
    return funcPromise(true)
  }));

  console.log(res) // ["ok", "ok", "ok"]
})();

めでたし、めでたし・・・。 ではない!!!

rejectが考慮されていない

もちろん、全ての非同期処理の成功が保証されているのであれば、Promise.allで良いかと思います。ただ、実際は、そんなうまいこといかないですよね。ってことで、上のコードにrejectされるPromiseも加えてみましょう。

const funcPromise = (result) => {
  return new Promise((resolve, reject) => {
    if (result) {
      resolve("ok")
      return
    }
    reject("error")
  })
}

(async () => {
  const array = [1,2,3];

  const res = await Promise.all(array.map( (v) => {
    return funcPromise(v % 2 === 0)
  }));

  console.log(res) // Promise {<rejected>: "erroreee"}
})();

今度は、偶数ならresolveされて、奇数ならrejectされるように変更しました。 すると、残念ながら、rejectされたPromiseオブジェクトが返ってきてしまいました。何かしらの配列が欲しかったのに、期待したものとは違いますね。

Promise.allは内部のPromiseオブジェクトが全てresolveされた時にFulfilledになり、一つでもrejectされた時は、Rejectedになってしまうからですね。 (そもそも await使うならちゃんとtry-catchしろよというのは、一旦置いときましょう)

失敗する可能性のある非同期のループ処理はどう書くか

Promise.allSettledを使うのが良いのではないでしょうか?

こんな感じ。

const funcPromise = (result) => {
  return new Promise((resolve, reject) => {
    if (result) {
      resolve("ok")
      return
    }
    reject("error")
  })
}

(async () => {
  const array = [1,2,3];

  const res = await Promise.allSettled(array.map( (v) => {
    return funcPromise(v % 2 === 0)
  }));

  console.log(res.map(r => r.value)) // [undefined, "ok", undefined]
})();

Promise.allSettled は全てのPromiseがPendingでなくなるまで、処理を行ってくれます。rejectされるPromiseがいても、他のPromiseがFulfilled or Rejectedなるまで処理を続けます。

どさくさに紛れて、コンソール出力の部分も修正してます。

console.log(res.map(r => r.value))

Promise.allがresolveされた値だけを返すのに対して、Promise.allSettledは以下のようなオブジェクトを返します。

 {status: "fulfilled", value: ${resolveされた値}},
 or
 {status: "rejected",  reason: ${rejectされた値}}

まとめ

ネット上の情報は鵜呑みにしない。 この記事を書いてる本人も弱々エンジンなので、間違ってる可能性が往々にしてあります。

業務等で使うようなコードを書く場合は、テストコードをしっかり書いて、MDNや書籍などをしっかり確認しないとダメだめですね。

Promise.allSettled() - JavaScript | MDN

← PrevNext →
  • @masayuki031