3

在 Map 遍历中使用 async 函数

 1 year ago
source link: https://juzhiyuan.me/blog/async-in-map
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在 Map 遍历中使用 async 函数

有时需要使用 Sleep 函数阻塞代码一段时间,该函数常规实现与调用方式如下:

// Sleep Function
const sleep = (ms) =>
  new Promise((resolve) => setTimeout(resolve, ms))(
    // Usage
    async () => {
      await sleep(3000)
    }
  )

但在 Array.prototype.map 中使用时,却有着错误的表现,具体如下:

// 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 钟时间后再继续执行;但实际表现是:每当执行

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 方法,其返回值为

// mapResult [ Promise { <pending> }, Promise { <pending> } ]

即包含了多个状态为 pending 的 Promise 的数组!

另外,如果只是循环而不需要操作 map 返回的数组,那么也应当使用 for 循环。

对于 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]

WechatIMG3.png

  1. http://devcheater.com/
  2. https://codeburst.io/javascript-async-await-with-foreach-b6ba62bbf404
  3. https://zellwk.com/blog/async-await-in-loops/

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK