async
與 await
的使用方式相對簡單。 蛤當你嘗試在迴圈中使用await
時,事情就會變得複雜一些。
想閱讀更多優質文章請猛戳GitHub部落格,一年百來篇優質文章等著你!
在本文中,分享一些在如果迴圈中使用await
值得注意的問題。
準備一個例子
對於這篇文章,假設你想從水果籃中獲取水果的數量。
const fruitBasket = {
apple: 27,
grape: 0,
pear: 14
};
你想從fruitBasket
獲得每個水果的數量。 要獲取水果的數量,可以使用getNumFruit
函式。
const getNumFruit = fruit => {
return fruitBasket[fruit];
};
const numApples = getNumFruit('apple');
console.log(numApples); //27
現在,假設fruitBasket
是從伺服器上獲取,這裡我們使用 setTimeout
來模擬。
const sleep = ms => {
return new Promise(resolve => setTimeout(resolve, ms))
};
const getNumFruie = fruit => {
return sleep(1000).then(v => fruitBasket[fruit]);
};
getNumFruit("apple").then(num => console.log(num)); // 27
最後,假設你想使用await
和getNumFruit
來獲取非同步函式中每個水果的數量。
const control = async _ => {
console.log('Start')
const numApples = await getNumFruit('apple');
console.log(numApples);
const numGrapes = await getNumFruit('grape');
console.log(numGrapes);
const numPears = await getNumFruit('pear');
console.log(numPears);
console.log('End')
}
在 for 迴圈中使用 await
首先定義一個存放水果的陣列:
const fruitsToGet = [“apple”, “grape”, “pear”];
迴圈遍歷這個陣列:
const forLoop = async _ => {
console.log('Start');
for (let index = 0; index < fruitsToGet.length; index++) {
// 得到每個水果的數量
}
console.log('End')
}
在for
迴圈中,過上使用getNumFruit
來獲取每個水果的數量,並將數量列印到控制檯。
由於getNumFruit
返回一個promise
,我們使用 await
來等待結果的返回並列印它。
const forLoop = async _ => {
console.log('start');
for (let index = 0; index < fruitsToGet.length; index ++) {
const fruit = fruitsToGet[index];
const numFruit = await getNumFruit(fruit);
console.log(numFruit);
}
console.log('End')
}
當使用await
時,希望JavaScript暫停執行,直到等待 promise 返回處理結果。這意味著for
迴圈中的await
應該按順序執行。
結果正如你所預料的那樣。
“Start”;
“Apple: 27”;
“Grape: 0”;
“Pear: 14”;
“End”;
這種行為適用於大多數迴圈(比如while
和for-of
迴圈)…
但是它不能處理需要回撥的迴圈,如forEach
、map
、filter
和reduce
。在接下來的幾節中,我們將研究await
如何影響forEach
、map和filter
。
在 forEach 迴圈中使用 await
首先,使用 forEach
對陣列進行遍歷。
const forEach = _ => {
console.log('start');
fruitsToGet.forEach(fruit => {
//...
})
console.log('End')
}
接下來,我們將嘗試使用getNumFruit
獲取水果數量。 (注意回撥函式中的async
關鍵字。我們需要這個async
關鍵字,因為await
在回撥函式中)。
const forEachLoop = _ => {
console.log('Start');
fruitsToGet.forEach(async fruit => {
const numFruit = await getNumFruit(fruit);
console.log(numFruit)
});
console.log('End')
}
我期望控制檯列印以下內容:
“Start”;
“27”;
“0”;
“14”;
“End”;
但實際結果是不同的。在forEach
迴圈中等待返回結果之前,JavaScrip先執行了 console.log('End')。
實際控制檯列印如下:
‘Start’
‘End’
‘27’
‘0’
‘14’
JavaScript 中的 forEach
不支援 promise 感知,也支援 async
和await
,所以不能在 forEach
使用 await
。
在 map 中使用 await
如果在map
中使用await
, map
始終返回promise
陣列,這是因為非同步函式總是返回promise
。
const mapLoop = async _ => {
console.log('Start')
const numFruits = await fruitsToGet.map(async fruit => {
const numFruit = await getNumFruit(fruit);
return numFruit;
})
console.log(numFruits);
console.log('End')
}
“Start”;
“[Promise, Promise, Promise]”;
“End”;
如果你在 map
中使用 await
,map
總是返回promises
,你必須等待promises
陣列得到處理。 或者通過await Promise.all(arrayOfPromises)
來完成此操作。
const mapLoop = async _ => {
console.log('Start');
const promises = fruitsToGet.map(async fruit => {
const numFruit = await getNumFruit(fruit);
return numFruit;
});
const numFruits = await Promise.all(promises);
console.log(numFruits);
console.log('End')
}
執行結果如下:
如果你願意,可以在promise
中處理返回值,解析後的將是返回的值。
const mapLoop = _ => {
// ...
const promises = fruitsToGet.map(async fruit => {
const numFruit = await getNumFruit(fruit);
return numFruit + 100
})
// ...
}
“Start”;
“[127, 100, 114]”;
“End”;
在 filter 迴圈中使用 await
當你使用filter
時,希望篩選具有特定結果的陣列。假設過濾數量大於20的陣列。
如果你正常使用filter
(沒有 await),如下:
const filterLoop = _ => {
console.log('Start')
const moreThan20 = fruitsToGet.filter(async fruit => {
const numFruit = await fruitBasket[fruit]
return numFruit > 20
})
console.log(moreThan20)
console.log('END')
}
執行結果
Start
["apple"]
END
filter
中的await
不會以相同的方式工作。 事實上,它根本不起作用。
const filterLoop = async _ => {
console.log('Start')
const moreThan20 = await fruitsToGet.filter(async fruit => {
const numFruit = fruitBasket[fruit]
return numFruit > 20
})
console.log(moreThan20)
console.log('END')
}
// 列印結果
Start
["apple", "grape", "pear"]
END
為什麼會發生這種情況?
當在filter
回撥中使用await
時,回撥總是一個promise
。由於promise
總是真的,陣列中的所有項都通過filter
。在filter
使用 await
類以下這段程式碼
const filtered = array.filter(true);
在filter
使用 await
正確的三個步驟
- 使用
map
返回一個promise 陣列 - 使用
await
等待處理結果 - 使用
filter
對返回的結果進行處理
const filterLoop = async _ => {
console.log('Start');
const promises = await fruitsToGet.map(fruit => getNumFruit(fruit));
const numFruits = await Promise.all(promises);
const moreThan20 = fruitsToGet.filter((fruit, index) => {
const numFruit = numFruits[index];
return numFruit > 20;
})
console.log(moreThan20);
console.log('End')
}
在 reduce 迴圈中使用 await
如果想要計算 fruitBastet
中的水果總數。 通常,你可以使用reduce
迴圈遍歷陣列並將數字相加。
const reduceLoop = _ => {
console.log('Start');
const sum = fruitsToGet.reduce((sum, fruit) => {
const numFruit = fruitBasket[fruit];
return sum + numFruit;
}, 0)
console.log(sum)
console.log('End')
}
執行結果:
當你在 reduce
中使用await
時,結果會變得非常混亂。
const reduceLoop = async _ => {
console.log('Start');
const sum = await fruitsToGet.reduce(async (sum, fruit) => {
const numFruit = await fruitBasket[fruit];
return sum + numFruit;
}, 0)
console.log(sum)
console.log('End')
}
[object Promise]14
是什麼 鬼??
剖析這一點很有趣。
- 在第一次遍歷中,
sum
為0
。numFruit
是27
(通過getNumFruit(apple)
的得到的值),0 + 27 = 27
。 - 在第二次遍歷中,
sum
是一個promise
。 (為什麼?因為非同步函式總是返回promises
!)numFruit
是0
.promise 無法正常新增到物件,因此JavaScript將其轉換為[object Promise]
字串。[object Promise] + 0
是object Promise] 0
。 - 在第三次遍歷中,
sum
也是一個promise
。numFruit
是14
.[object Promise] + 14
是[object Promise] 14
。
解開謎團!
這意味著,你可以在reduce
回撥中使用await
,但是你必須記住先等待累加器!
const reduceLoop = async _ => {
console.log('Start');
const sum = await fruitsToGet.reduce(async (promisedSum, fruit) => {
const sum = await promisedSum;
const numFruit = await fruitBasket[fruit];
return sum + numFruit;
}, 0)
console.log(sum)
console.log('End')
}
但是從上圖中看到的那樣,await
操作都需要很長時間。 發生這種情況是因為reduceLoop
需要等待每次遍歷完成promisedSum
。
有一種方法可以加速reduce
迴圈,如果你在等待promisedSum之前先等待getNumFruits()
,那麼reduceLoop
只需要一秒鐘即可完成:
const reduceLoop = async _ => {
console.log('Start');
const sum = await fruitsToGet.reduce(async (promisedSum, fruit) => {
const numFruit = await fruitBasket[fruit];
const sum = await promisedSum;
return sum + numFruit;
}, 0)
console.log(sum)
console.log('End')
}
這是因為reduce
可以在等待迴圈的下一個迭代之前觸發所有三個getNumFruit
promise。然而,這個方法有點令人困惑,因為你必須注意等待的順序。
在reduce中使用wait最簡單(也是最有效)的方法是
- 使用
map
返回一個promise 陣列 - 使用
await
等待處理結果 - 使用
reduce
對返回的結果進行處理const reduceLoop = async _ => {
console.log('Start');const promises = fruitsToGet.map(getNumFruit);
const numFruits = await Promise.all(promises);
const sum = numFruits.reduce((sum, fruit) => sum + fruit);console.log(sum)
console.log('End')
}
這個版本易於閱讀和理解,需要一秒鐘來計算水果總數。
從上面看出來什麼
- 如果你想連續執行
await
呼叫,請使用for
迴圈(或任何沒有回撥的迴圈)。 - 永遠不要和
forEach
一起使用await
,而是使用for
迴圈(或任何沒有回撥的迴圈)。 - 不要在
filter
和reduce
中使用await
,如果需要,先用map
進一步驟處理,然後在使用filter
和reduce
進行處理。
交流
乾貨系列文章彙總如下,覺得不錯點個Star,歡迎 加群 互相學習。
https://github.com/qq44924588...
我是小智,公眾號「大遷世界」作者,對前端技術保持學習愛好者。我會經常分享自己所學所看的乾貨,在進階的路上,共勉!
關注公眾號,後臺回覆福利,即可看到福利,你懂的。