原文 https://blog.shaoyaoju.org/javascript-async-with-map/
有時須要使用 Sleep 函數阻塞代碼一段時間,該函數常規實現與調用方式以下:javascript
// Sleep Function const sleep = async ms => new Promise(resolve => setTimeout(resolve, ms)) // Usage (async () => { await sleep(3000) })
但在 Array.prototype.map 中使用時,卻有着錯誤的表現,具體以下:java
// code snippet 1 [1, 2].map(async num => { console.log('Loop Start') console.log(num) await sleep(3000) console.log('Loop End') }) // expected output // Loop Start // 1 // Wait for about 3s // Loop End // Loop Start // 2 // Wait for about 3s // Loop End // Actual output // Loop Start // 1 // Loop Start // 2 // Wait for about 3s // Loop End // Loop End
咱們指望的是,在每一次循環時,暫停約 3s 鍾時間後再繼續執行;但實際表現是:每當執行git
await sleep(3000)
時,沒有等待結果返回便進入到了下一次循環。之因此產生錯誤的表現,緣由是:github
當 async 函數被執行時,將當即返回 pending 狀態的Promise( Promise 是 Truthy 的)!所以,在 map 循環時,不會等待 await 操做完成,而是直接進入下一次循環,因此應當配合 for 循環使用 async。數組
驗證一下,咱們將 code snippet 1 作一下修改:併發
// code snippet 2 const sleep = ms => new Promise(resolve => { console.log('sleep') setTimeout(() => { console.log('resolve') resolve() }, ms) }) const mapResult = [1, 2].map(async num => { console.log('Loop Start') console.log(num) await sleep(3000) console.log('Loop End') }) console.log('mapResult', mapResult) // Actual output // Loop Start // 1 // sleep // Loop Start // 2 // sleep // mapResult [ Promise { <pending> }, Promise { <pending> } ] // resolve // Loop End // resolve // Loop End
能夠看到,使用了 async 函數後的 map 方法,其返回值爲異步
// mapResult [ Promise { <pending> }, Promise { <pending> } ]
即包含了多個狀態爲 pending 的 Promise 的數組!async
另外,若是隻是循環而不須要操做 map 返回的數組,那麼也應當使用 for 循環。函數
對於 forEach 而言,參考 MDN 中它的 Polyfill 可知,若回調函數爲異步操做,它將會表現出併發的狀況,由於它不支持等待異步操做完成後再進入下一次循環。oop
感謝 @楊寧 提供的使用 Array.prototype.reduce 解決的方法:
// https://codepen.io/juzhiyuan/pen/jONwyeq const sleep = wait => new Promise(resolve => setTimeout(resolve, wait)); const __main = async function() { // 你的需求實際上是在一組 task 中,循環執行,每一個都 sleep,並返回結果 const tasks = [1, 2, 3]; let results = await tasks.reduce(async (previousValue, currentValue) => { // 這裏是關鍵,須要 await 前一個 task 執行的結果 // 實際上每一個 reduce 每一個循環執行都至關於 new Promise // 但第二次執行能夠拿到上一次執行的結果,也就是上一個 Promise // 每次執行只要 await 上一個 Promise,便可實現依次執行 let results = await previousValue console.log(`task ${currentValue} start`) await sleep(1000 * currentValue); console.log(`${currentValue}`); console.log(`task ${currentValue} end`); results.push(currentValue) return results }, []); console.log(results); } __main() // Actual output: // task 1 start // 1 // task 1 end // task 2 start // 2 // task 2 end // task 3 start // 3 // task 3 end // [1, 2, 3]