【JavaScript】複数のPromise, async, awaitを組み合わせる

 よくasync, await(Promiseの言い換え構文)で非同期処理を同期処理的に記述します。この時、全てを単純に同期化するのでなく複数の非同期処理をいくらかまとめて同期化したいなど特殊な場合が多々あります。そういった時に使える方法を紹介します。

複数の非同期処理を同時に走らせて全てを待つ

 よくある書き方に次があります。

const user = await getUser() // ユーザをAPI越しに非同期的に取得
const post = await getPost() // 投稿をAPI越しに非同期的に取得
createUserPost(user, post); // ユーザと投稿からユーザ投稿を生成

 これはユーザと投稿を取得し、それを元に処理を行う処理です。もしユーザと投稿がそろっていないままcreateUserPost関数が走り出すと未定義参照で思った通りの処理になりません。待つ必要があります。上記プログラムは await によりちゃんと待つようになっています。しかし、一連の処理において投稿の取得はユーザの取得を待つ必要がありません。ユーザと投稿を非同期で同時に取りに行き、二つ揃うのを待つ方が速度面で優秀そうです。これを実現するには次です。

const userAndPost = await Promise.all([getUser(), getPost()]);
const user = userAndPost[0];
const post = userAndPost[1];
createUserPost(user, post); // ユーザと投稿からユーザ投稿を生成

 Promise.allは引数の取った配列内のPromise全てが終わるのを待つメソッドです。allメソッドの返り値は引数の Promise の中身を集めた配列です。注目すべき点としてallメソッドは引数の非同期処理のいずれかが失敗に終わった時点で全処理を中断します。すべての非同期処理を可能な限り実行したい場合はallSettledメソッドを用いるべきです。
Promise.all() – JavaScript | MDN
Promise.allSettled() – JavaScript | MDN
 余談として行ってはいけないパターンが次です。

let userDone;
let postDone;
getUser().finally(()=>{userDone = true})
getPost().finally(()=>{postDone = true})
while(userDone === undefined || postDone === undefined){}
createUserPost(user, post);

 一見してwhileで待ち、非同期処理が終了次第進みそうですがそうはなりません。whileの処理が計算資源の何かしらを食いつぶしてプログラムは無限ループで止まります(少なくともGoogle Chromeで確認)。

複数の情報源から最速でデータを取得したい時

 複数の非同期処理の内、何か一つが完了した時点で処理を打ち切りたい場合にはPromise.raceメソッドがあります。このメソッドは最初に完了した非同期処理の結果を返します。例えば次の様に使えます。

// 複数サーバのAPIに問い合わせて一番最初に返ってきたものを採用
const user = await Promise.race([
    HogeServerUserRepository.get(userId),
    FugaServerUserRepository.get(userId),
    PiyoServerUserRepository.get(userId),
]);

Promise.race() – JavaScript | MDN

raceとallを組み合わせる

 await Promise.all(), await Promise.race()と記述してある通り、これらのメソッドが返すものもまたPromiseオブジェクトです。このため、allをand、raceをor、各Promiseを要素と見なすと論理式的に記述ができます。例えば次です。

// 複数サーバのAPIにユーザ情報と投稿情報を問い合わせ
// 最も早く帰ってきたユーザと投稿を元にユーザ投稿を生成
// (UserFromHoge || UserFromPiyo) && (PostFromHoge || PostFromFuga) な感じです
const userAndPost = Promise.all([
    Promise.race([
        HogeServerUserRepository.get(userId),
        PiyoServerUserRepository.get(userId),
    ]),
    Promise.race([
        HogeServerPostRepository.get(postId),
        FugaServerPostRepository.get(psotId),
    ]),
]);
const user = userAndPost[0];
const post = userAndPost[1];
createUserPost(user, post); // ユーザと投稿からユーザ投稿を生成
>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG