はじめに
JavaScriptにおいて、for文中で同期処理を利用したい場合にasync/awaitでなくPromise.all()というメソッドを用いると良い、という話です。
自分自身がasync/awaitで反復処理を行い非常に実行に時間を要して困っていたため、記事にしてみました。
そもそもJavaScriptの同期/非同期処理とは
そもそも、JavaScriptは同期処理であり、非同期処理でもあるプログラミング言語です。
基本的にはコードを上から実行しますが、時間がかかる処理は終了を待たずに進んでいきます。
有名な事例ですが......
console.log("A");
setTimeout(() => {
console.log("B");
}, 1000);
console.log("C");
上記の場合、"A"が出力され、次にsetTimeout関数が実行され、1秒後にBが出力されることになります。
待ってる間もプログラムの実行が進むので、先に"C"の出力が行われ、最後に1秒後の"B"が出力されます。
Promiseとasync/await
例えば外部APIのやり取りなど時間のかかる処理を行い、その後その結果を元にプログラムの実行を進行したい場合、外部APIとのやりとりの完了を待ってその後プログラムを実行したい。という状況になるかと思います。
そのような場合に同期処理としてPromiseもしくはasync/awaitというメソッドを用います。
ES2015の時点ではPromiseというメソッドのみでしたが、ES2017でasync/awaitが登場し、こちらの方が簡単で、読み手としても書き手としても理解しやすいため、特に初心者エンジニアはこちらを頻繁に使っているのではないでしょうか。
単純に「この処理が終わるまで待ちたい」というシチュエーションであればasync/awaitの方がマッチしています。
async/awaitでうまくいかなかった
私自身も使いやすいasync/awaitを主に使用していました。
しかし、反復処理で時間のかかる処理を連続で行いたい場合に、非常に時間を要することに困っていました。
// 外部APIを処理する関数
async function externalAPIProcess (user) {
// 時間のかかる処理
return userResult;
}
// 非常に時間がかかってしまう......
const users = [user1, user2, user3, user4, user5];
const results =[];
for (const user of users) {
const userResult = await externalAPIProcess(user);
results.push(userResult);
}
console.log(results);
Promise.all()の利用
Promise.all()というメソッドを用いることでこれを解決することができました。
async/awaitのみでの記述では、要素数に比例して実行時間が伸びていきます。
しかし、反復処理中では同期処理を行わず、await Promise.all()
を反復処理終了後に行うことで、処理時間を大幅に短縮できます。
メモリと処理量にもよりますが、理論上要素数が増えても実行時間は変わりません。
const users = [user1, user2, user3, user4, user5];
const resultPromises =[];
for (const user of users) {
const userResult = externalAPIProcess(user);
resultPromises.push(userResult);
}
const results = await Promise.all(resultPromises);
console.log(results);
私の場合でも、大幅な短縮を図ることができました。
単純なメソッドかもしれないですが、長期間悩んでいたことがあっさり解決できたので記事に上げてみました。
結論
JavaScriptの同期処理はasync/awaitがわかりやすく使いやすいですが、
Promise.all()などasync/awaitだけでは実現できない機能も多いです。
参考文献