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や書籍などをしっかり確認しないとダメだめですね。