在 Map 遍歷中使用 async 函數

原文 blog.shaoyaoju.org/javascript-…javascript

有時須要使用 Sleep 函數阻塞代碼一段時間,該函數常規實現與調用方式以下:java

// Sleep Function
const sleep = async ms => new Promise(resolve => setTimeout(resolve, ms))

// Usage
(async () => {
   await sleep(3000)
})
複製代碼

但在 Array.prototype.map 中使用時,卻有着錯誤的表現,具體以下:git

// 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 鍾時間後再繼續執行;但實際表現是:每當執行github

await sleep(3000)
複製代碼

時,沒有等待結果返回便進入到了下一次循環。之因此產生錯誤的表現,緣由是:數組

當 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 方法,其返回值爲async

// mapResult [ Promise { <pending> }, Promise { <pending> } ]
複製代碼

即包含了多個狀態爲 pending 的 Promise 的數組!函數

另外,若是隻是循環而不須要操做 map 返回的數組,那麼也應當使用 for 循環。oop

對於 forEach 而言,參考 MDN 中它的 Polyfill 可知,若回調函數爲異步操做,它將會表現出併發的狀況,由於它不支持等待異步操做完成後再進入下一次循環。

感謝 @楊寧 提供的使用 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]
複製代碼

參考

  1. devcheater.com/
  2. codeburst.io/javascript-…
  3. zellwk.com/blog/async-…

本篇文章由一文多發平臺ArtiPub自動發佈

相關文章
相關標籤/搜索