在 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自动发布

相关文章
相关标签/搜索