mapやfilter関数のcallbackに非同期関数を設定してうまく動かないという事案があったので、Tipsとしてまとめます。
非同期処理を定義する
とりあえず非同期処理を行う関数を作成してみます。3000msの間、ブロッキングをして100を返す非同期関数です。
const returnPromise = async () => {
return new Promise<number>(
(resolve) => {
setTimeout(() => {
resolve(100);
}, 3000);
}
);
}
const main = async () => {
const num = await returnPromise();
console.log(num);
}
main();配列の各要素に対してfilter関数を適用してみる
[1, 35, 100]からretrunPromiseの返り値100を比較して同じものだけを返そうとしてみます。
理論通り動くなら、[1, 35, 100] が[100] となって出力されるはず、、、
const returnPromise = async () => {
return new Promise<number>(
(resolve) => {
setTimeout(() => {
resolve(100);
}, 3000);
}
);
}
const main = async () => {
const array = [1,35,100];
const filteredArray = array.filter(async (value) => {
const num = await returnPromise();
if (value === num) {
return true;
}else{
return false;
}
})
console.log(filteredArray);
}
main();しかしながら、consoleに出力されるのは[1, 35, 100] 。
filter関数は同期的な処理しか想定しない
async (value) => {
const num = await returnPromise();
if (value === num) {
return true;
}else{
return false;
}
}filter関数のcallback関数が非同期関数になっています。よって、この関数の返り値はPromise<boolean>が返ってきます。JSにおいて、false, 0, -0, 0n, "", null, undefined, NaNを除くすべての値は真値となるので、Promise<boolean>は真値になります。
Truthy (真値) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN
そうすると、配列の各要素に対しては必ず真値(Promise<boolean>)が返るのでfilterされず、そのまま結果が返されてしまうわけです。
解決策
Promiseをちゃんと集める
map関数を使用してPromiseを集めて、それをPromise.allで解決するまで待てば、きちんとfilterできます。
まず、Promiseを集めます。そのあと、trueになったindexに対してfilterします。
const main = async () => {
const array = [1,35,100];
const promiseOfArray = array.map(async (value) => {
const num = await returnPromise();
if (value === num) {
return true;
}else{
return false;
}
})
const results = await Promise.all(promiseOfArray);
const filteredArray = array.filter((_, index) => results[index])
console.log(filteredArray);
}このQiitaの記事を参考にしてみました。
JavaScriptで配列のfilterにasync関数を使いたい #JavaScript - Qiita
for文で実装する
非同期処理を使わずに行うとこうなります。
const main = async () => {
const array = [1,35,100];
const filteredArray = [];
for (const value of array){
const num = await returnPromise();
if(value === num){
filteredArray.push(value);
}
}
console.log(filteredArray);
}Promise.all VS for文
同じように見えて実行時間に大きな差があります。
Promise.allを使用する場合はreturnPromise()が非同期で実行されるので、returnPromise()が並列に3つ実行されます。実行時間は約3秒です。
一方、for文は、returnPromise()が同期的に順に3つ実行されます。実行時間は3秒×3=約9秒です。
3倍時間がかかるわけで、for文での実装は実行時間の面で大きく劣ると考えられます。
さいごに
Promiseで実装すれば、実行時間が短縮されるということで、非同期処理ってすごいんだなと実感できました。