長引くコロナ禍の中、お家時間をより充実したものにするために社内開発メンバーの皆さんにオススメ家電を聞いたり実際の使用感の伺ったりできるslackチャンネルを作ったことが上半期の自身の業務成果かなと思っているフロントエンドエンジニアの田原です。
\(^o^)/(お仕事もしてます)
生活スタイルが皆さん異なるのでオススメ家電や感想等が違い色んな生の意見をもらえるので新しい発見もあり、まだまだリモートワークが続く中で比較的どんな人でも (家電は誰でもつかうと思うので)参加しやすい話題な気もしているのでコミュニケーションの一助にもなっていたら良いなと思ったりしております。
チームリーダーオススメのNature Remoはこの夏に購入して(というかこれ書いてる時にポチった)色んな家電をより使いやすくしたいなと画策しており、先行予約で手に入れたAladdin Connectorはまだ開封すらしていませんが、そろそろNintendo Switchと接続して夏のお家時間をより充実させたものにしたいなと思っております。
お話かわりまして、ES2021で追加予定の新しいメソッドのanyも含めてPromiseの静的メソッドについて違いや使い所等について今回は触れていきたいなと思います。
※尚、JavaScriptにおける非同期処理についてやPromiseの概要、await/async等については世の中に良い記事が溢れているので説明は割愛させて頂きます。
目次
4つの静的メソッド
1つの非同期処理についてはawait/asyncを使うことが多くなってきたかと思いますが、複数処理を扱う際のメソッドとして実験的なanyを含めて4つの静的メソッドが存在しており、これらを適所で使うことで非同期処理のハンドリングをよりパフォーマンスの良いものにすることが可能になります。
Promise.all
非同期処理を並列で同時実行しすべての非同期処理が解決(正常終了)するか、同時実行の内の1つでも拒否(Error等)されるまで待ちます。
【人で例えると】
約束の内容は全部守ろうとしてくれる義理堅い人(おそらく)
e.g.
async function all() { const first = new Promise((resolve, reject) => { setTimeout(() => resolve('1秒後に処理が完了'),1000) }) const second = new Promise((resolve, reject) => { setTimeout(() => resolve('10秒後に処理が完了'),10000) }) const result = await Promise.all([first, second]).catch((e) => cosole.info('error', e)) if (result) { console.info('success', result) } } all()
【使い所】
複数のエンドポイントに並列的にrequestを投げ、全てのresponseが正常でない場合はerrorハンドリングする場合等
Promise.allSettled
非同期処理を並列で同時実行しすべての非同期処理が解決or拒否されるまで待ちます。
allは1つでも非同期処理がrejectされると途中まで走っていた別の処理の状況は無視して処理を止めてしまいますが、allSettledは各処理がどちらの 処理になろうとも設定された内容を返すまで待ちます。
ただし、rejectを行ってもcatch節に入らず
(その為、allSettledの場合、catchは書かなくて良い)に必ず値が返り、
返ってくる値の内容も特殊で以下のようなobjectの形で返却されます。
{status: "fulfilled", value: xxxx } or {status: "rejected", reason: xxxx }
【人で例えると】
上手くいっても失敗しても最後まで約束したことはこなそうとしてくれる義理堅い人(きっと)
e.g.
async function allSettled() { const first = new Promise((resolve, reject) => { setTimeout(() => resolve('1秒後に処理が完了'),1000) }) const second = new Promise((resolve, reject) => { setTimeout(() => resolve('10秒後に処理が完了'),10000) }) const result = await Promise.allSettled([first, second]) console.info('success', result) } allSettled()
- Promise.allSettled 2つとも成功:10秒後にstatus: fulfilledで2つとも返される
- Promise.allSettled 2つとも失敗:10秒後にstatus: rejectedで2つとも返される
- Promise.allSettled 1つ成功1つ失敗:10秒後にstatus: fulfilledとstatus: rejectedで1つずつ返される
【使い所】
複数のエンドポイントに並行的にrequestを投げるが、各responseの状態次第で振る舞いを変えたいとき等
Promise.race
非同期処理を並列で同時実行しどれか1つが解決or拒否されるまで待ちます。 拒否時はall同じになりますが、allと違うのは1つでも解決した場合はすぐに処理が終わるところです。
以下の例ではfirstが解決であっても拒否であっても必ず先に処理が走る為(1000msなので10000msより早い)、secondの処理は待たれることはありません。 番外編で後述しておりますが単体よりも組み合わせて使う際に効果を発揮する気がしております。
【人で例えると】
できる約束の内容を最速で対応してくれる義理堅い人(だと思う)
e.g.
async function race() { const first = new Promise((resolve, reject) => { setTimeout(() => resolve('1秒後に処理が完了'),1000) }) const second = new Promise((resolve, reject) => { setTimeout(() => resolve('10秒後に処理が完了'),10000) }) const result = await Promise.race([first, second]).catch((e) => console.info('error', e)) if(result){ console.info('success', result) } } race()
【使い所】
複数のエンドポイントに並行的にrequestを投げ、一番早く処理される内容次第でハンドリングしたい時等
Promise.any
ES2021に実装される実験的メソッドであり、1つでも解決した時点で終了します。
raceと似ていますが、raceは解決であっても拒否であっても
であったのに対し、1つが解決しなくても他のものが解決すればその処理が終わるまで待ちます。
その為、全ての処理が拒否された時にのみcatch
節に入ります。
【人で例えると】
約束が全部こなせないとわかるまで投げ出さない義理堅い人(多分)
e.g.
async function any() { const first = new Promise((resolve, reject) => { setTimeout(() => resolve('1秒後に処理が完了'),1000) }) const second = new Promise((resolve, reject) => { setTimeout(() => resolve('10秒後に処理が完了'),10000) }) const result = await Promise.any([first, second]).catch((e) => console.info('error', e)) if(result){ console.info('success', result) } } any()
- Promise.any 両方成功:1秒後に値が返る(配列では無い)
- Promise.any 片側が成功:10秒後にsecondの処理が成功し値が返る(配列では無い)
- Promise.any 両方失敗:10秒後(secondの処理が失敗した後)にAggregateError: All promises were rejectedのerrorが返る(配列で返らないのでrejectで設定しているerror内容等はerrorオブジェクトの中に入っていないdefaultのerror内容となる)
【使い所】
並行的にrequestを投げた結果、全て拒否されるまではハンドリングするのを待ちたい&全部揃ってなくても返されるresponseだけで良い場合等
番外編
ちなみにここまで説明した各メソッドで他のメソッドをラップして実行を待つこともできます。
e.g.
async function allRace() { const first = new Promise((resolve, reject) => { setTimeout(() => resolve('1秒後に処理が完了'),1000) }) const second = new Promise((resolve, reject) => { setTimeout(() => resolve('10秒後に処理が完了'),10000) }) const result = await Promise.all([ Promise.race([first, second]), Promise.race([first, second]) ]).catch((e) => console.info('error', e)) if(result){ console.info('success', result) } } allRace()
- この場合all->raceの処理となり:1秒後にfirstが2回raceとして返され、全ての値が返った状態のallとして配列が返ります。
- その他、all->rase,allSettledの処理等もできるので組み合わせ次第で色んな待ち方ができそうです。
まとめ
各Promiseメソッドは人に例えるとみんな義理堅い人だったことがわかり.....
弊社フロントエンドではNuxtの機能であるserverMiddlewareを使ってBFFを導入しており、BFFサーバーの実装の中でAPIへのrequest処理をいい感じにしたい部分があったので色々と調べた結果、anyメソッドの登場もあったので(anyについては知らなかったので)備忘録も兼ねて書きました。 改めて色々と調べたりしたのでPromise完全に理解した(してない。でも少しは理解が深まった。)気がします。
弊社内のフロントの構成は以下スライドに内容記載しておりますのでご興味あればご一読頂けると嬉しいです。