本投稿のサンプルコードを GitHub に用意しました。
im36-123/multiple_await | GitHub
概要
async/await を使うとで非同期処理を同期処理のように記述できますが、複数の非同期処理を待つときに気をつけたいことを紹介します。
複数のリクエスト送信して、それらのレスポンスからページを構成するときなどに役立つかもしれません。
今回は Fetch API
を使って複数のリクエストを送信する場合を考えてみます。
普通に async/await を使う
まずは、ひとつだけリクエストを送信する場合を考えてみます。
function get(url) {
return fetch(url);
}
async function fn() {
const res = await get(`https://hoge/api`);
console.log(res);
}
fn();
fn()
が実行されると、 https://hoge/api
へリクエストを送信します。
レスポンスがあるまで、 fn()
内の処理は停止します。
特に問題ありませんね。
次は本題の複数のリクエストを待つ場合を見てみましょう
複数のリクエストを待つ
それでは2つ以上のリクエストを送信するときを考えてみます。
良くない書き方
先程のコードのリクエストを送信する部分を2つにしました(実際は for文などで loop させるかと思います。)。
function get(url) {
return fetch(url);
}
async function fn() {
const res1 = await get(`https://hoge/api`);
const res2 = await get(`https://fuga/api`);
console.log(res1);
console.log(res2);
}
fn();
こちらのコードは問題なく動作しますが、パフォーマンスが落ちてしまいます。
というのも、res1
に結果が代入されるまで fn()
内の処理が止まってしまうためです。
developer tool の Network タブを見てみると、確かに1つ目のリクエスト結果の後に、2つ目のリクエストが送信されています。
(※ 上図では Qiita の api にリクエストを送信しています。)
良い書き方
res1
の結果が res2
のリクエストに影響しない場合は、次のように複数のリクエストが並列して処理されるようにします。
先程のコードを修正して、2つのリクエストが並列して処理されるようにしました。
また、 Promise.all
を使ってすべてのリクエスト結果の返却を待ちましょう。
function get(url) {
return fetch(url);
}
async function fn() {
const results = [];
const urls = ['https://hoge/api', 'https://fuga/api'];
for (const url of urls) {
results.push(get(url));
}
console.log(await Promise.all(results));
}
fn();
こちらも developer tool の Network タブから確認してみましょう。
今回は、2つのリクエストが並行して処理されているのがわかります。
まとめ
業務で async/await を使っているときに ESLint に注意されたので書きました。
追記
@41semicolon さんよりコメントをいただきました…!!
fn()
に関してですがコメントで頂いた下記のコードの方がスマートで素敵です。
async function fn() {
const urls = ['https://hoge/api', 'https://fuga/api'];
console.log(await Promise.all(urls.map(get)));
}
呼び出し側で await するなどのときはこちら。
fn = (urls) => Promise.all(urls.map(get))
詳しくはコメントを参照お願いします。
参考
no-await-in-loop - Rules - ESLint - Pluggable JavaScript linter
async function - JavaScript | MDN
await - JavaScript | MDN