photo credit: EpicTop10.com Promise via photopin (license)
互いに独立した複数の非同期処理は、 Promise.all で待ちましょうね!ってことを書いています。
田中 賢一郎(著)
新品 ¥1,650 24個の評価
Amazon.co.jpで詳細を見る
例えば、複数のREST APIの結果を得たい場合、以下のように書きがちです。 一見問題なく見えますが、処理速度的にちょっと無駄なんですね。
実際ワタシも細かいことを気にせずにザクザクとコードを書いている時はこのように書いていて、あとから見直すことが多々あります。
async function sample() { const response1 = await fetch("http://path.to/rest/api1"); const response2 = await fetch("http://path.to/rest/api2"); }
上のコードでは、 最初のAPIの結果を得てから次のリクエストを行います。つまり順次実行されるのですが、これってちょっと無駄なんです。
APIの数が多くなればなるほど処理速度はどんどん伸びます。しかし、そのほとんどが結果を待っているだけの時間なので無駄なんです。 (他の非同期処理が行われるため一概に無駄とは言い切れませんが)
無駄なく実行するコード
複数の独立した非同期処理を素早く実行するには、以下のように書きます。
async function sample() { const [response1, response2] = await Promise.all([ fetch("http://path.to/rest/api1"), fetch("http://path.to/rest/api2"), ]); }
仮にAPIの数が10個になると、最初の例では約10倍の時間がかかります。でも Promise.all
を使った場合は、それほど時間は伸びません。
理論的には、ひとつのAPIの結果を得るまでの時間と同程度のはず(実際にはRESTサーバー側の処理能力に依存しますが)。
Promise.all
について
Promise.all
は、複数のPromiseオブジェクトがすべて完了するのを待ちます。
引数はPromiseオブジェクトの配列で、ひとつのPromiseオブジェクトを返します。
Promise.all
が返すPromiseオブジェクトは、引数で与えられたすべてのPromiseが解決した場合に解決し、
その結果は、引数で与えたPromise配列と同じ数の配列であり、各要素が解決した値となります。
(ここではすべてのPromiseの解決が成功する前提で書いています)
上のコードの例では、個々のAPI呼び出しが返すPromiseオブジェクトを、個別に awaitせず、配列に格納して Promise.all
に与えています。
これによって、すべてのAPIリクエストを一気に行って、結果が出るまで待つことになります。
複数非同期処理の並列実行で気をつけること
ここまでに書いたコードは、以下のように Array.map を使って置き換え可能です。 これが直感的であるかどうかは Promiseやasync/awaitに慣れているかどうかに依存すると思いますが、ここでワタシがよくやる間違いをひとつ書いておきます。
async function sample() { const apis = [ "http://path.to/rest/api1", "http://path.to/rest/api2", ]; const responses = await Promise.all(apis.map(api => fetch(api)); }
例えば、当初は非同期処理でなかったので Array.forEach
で書いていたのだが、途中で非同期処理に変更されたような時に、Array.forEach
を Array.map
に置き換えることを忘れていたり、
Promise.all
を await
するのを忘れていたり。これが実行時にエラーにならないんですよねえ。
できれば Array.forEach に AsyncFunction を与えた場合や、 Array.map の戻り値に対してなにも行っていない場合は警告を出してほしい。