本記事について
本記事では、JavaScriptの配列のmap関数の中で、async/awaitを使う方法について記載しました。
合わせて、async/awaitのイメージの仕方についても記載しました。
方法
- mapから呼び出す関数をasyncにする
- mapから呼び出す関数の中はawaitを使って同期処理にする
- mapから生成された配列をPromise.allに格納する
- Promise.allをawaitで受ける
例
// ダミー処理
function funcPromise(b){
return new Promise((resolve, reject)=>{
if(b){
resolve("OK");
} else {
reject("ERROR");
}
});
}
(async ()=>{
const array = [1,2,3];
const result = await Promise.all(array.map(async (v)=>{
const dummy = await funcPromise(true);
return dummy;
}));
console.log(result); // ["OK", "OK", "OK"]
})();
async/awaitのイメージの仕方
async/awaitについて「async/await 入門(JavaScript)」を参考にしました。詳しい解説はこちらの記事を参照してください。
ここではざっくりとasync/awaitのイメージの仕方について記載します。
基本ルール
- 「await」とは、「await <Promiseを返す関数>」と書くと、Promiseの結果が確定するまで待ってくれて、結果を返してくれる
- 「async」とは、「Promiseを返す関数」の書き方の1つ
- 「return <値>」で「resolve(<値>)」と同等のPromiseを返す
- 「throw <値>」で「reject(<値>)」と同等のPromiseを返す
Promiseを返す関数
asyncを使ったPromiseを返す関数とはどういったものになるのでしょうか?
従来のPromiseを使った場合と、asyncを使った場合は下記のようになり、2つは同等の処理になります。
// Promiseを使った場合
function funcPromise(b){
return new Promise((resolve, reject)=>{
if(b){
resolve("OK");
} else {
reject("ERROR");
}
});
}
// asyncを使った場合
async function funcAsync(b){
if(b){
return "OK";
} else {
throw "ERROR";
}
}
mapでの使用説明
以上を踏まえ、最初のmap使用例を見なおしてみます。
const result = await Promise.all(array.map(async (v)=>{
const dummy = await dummyAsync(true);
return dummy;
}));
まず、「map」で「async」関数が呼び出されているので、「return dummy」は成功値「dummy」を返す「Promise」を返します。
ですので、array.map()は、成功が格納された「Promise」の配列となります。
次に、「Promise.all()」は、こちらの解説のとおり、Promiseの配列を引数にとり、全てのPromiseが成功した場合は、各Promiseの「成功値」が入った配列を返すPromiseを返します。
最後に、awaitにより、Promiseから成功値の配列を取り出して、「result」に渡しています。
以上により、mapで各要素に対して非同期処理を行わせ、その結果の配列を同期的に取得することができるようになります。
その他
mapで、結果が格納されたPromiseの配列を作るところがミソで、サンプルでは「funcPromise()」が結果を格納したPromiseを返すので、コメントで指摘があったとおり、サンプルのmap部分は
array.map(
(v) => { return funcPromise(true); }
)
と簡潔に書くことができます。
mapから呼び出す関数の中で、「await」を使った処理を書く場合は、サンプル同様、mapから呼び出す関数にasyncを付ける必要があります。
mapから呼び出す関数をasync関数にすると、結果がバラバラになりそうな感じがしますが、結果をつなぎ留めておく糊的なものがPromiseであり、Promiseの完了をそろえるのがPromise.allで、そろった結果を取り出すのがawaitになります。
実は「const dummy = await dummyAsync(true)」の「await」を外しても動作します。しかし、async内ではawaitを使用して同期処理を行い、returnにはPromiseではなく結果が渡るように決めておいた方が、「その値には結果が格納されているのか?それともPromisが格納されているのか?」の把握がよりしやすくなるかと思います。
また、同様の処理をmapではなくfor文で書くこともできます
const result = [];
for(let i=0, len=array.length; i<len; ++i){
result.push(await funcAsync(true));
}
console.log(result);
const resultPromise = [];
for(let i=0, len=array.length; i<len; ++i){
resultPromise.push(funcAsync(true));
}
const result = await Promise.all(resultPromise);
console.log(result);
「for1.js」の方が分かりやすいのですが、配列の各要素を順に同期実行していくので処理に時間がかかります。
一方「for2.js」は、「map」を使った時と同様、並列処理が行われるのでより処理が早くなります。