有时需要使用 Sleep 函数阻塞代码一段时间,该函数常规实现与调用方式如下:
// Sleep Function const sleep = async 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]
本篇文章由一文多发平台ArtiPub自动发布
![]() | 1 popn74 2019-10-27 13:05:51 +08:00 添加 promise 数组 然后 promise.all |
![]() | 2 secondwtq 2019-10-27 13:07:13 +08:00 via iPhone 恭喜你重新实现了 mapM 的某个实例 |
![]() | 3 momocraft 2019-10-27 13:13:00 +08:00 div class="reply_content">map 的语义其实就更接近并行,需要等的用 reduce 更自然些 |
![]() | 4 yyfearth 2019-10-27 14:15:47 +08:00 ![]() @shaoyaoju 这么麻烦 要等待用 for of + await 就是了 非要用 forEach/map 或者 reduce 干嘛 并行用 Promise.all + array.map 就是 |
![]() | 5 sam014 2019-10-27 14:40:44 +08:00 我们期望的是,在每一次循环时,暂停约 3s 钟时间后再继续执行; --------------------------------------------------------------------------------------- 这种情况用 生成器 应该好做一些 |
![]() | 6 lqzhgood 2019-10-27 17:58:03 +08:00 via Android foreach 是并行 for 是串行 理论上不是 foreach 更快么… 我怎么记得 for 更快 等下起来验证一下 const a = new Array(10000); |
![]() | 7 terax 2019-10-27 18:31:46 +08:00 via iPhone 据我的理解,js async await 并不是阻塞代码的执行(只是 promise 语法糖,并不会改变 runtime )。可以理解为 await 之后的代码都被包在了 promise.then 里面了。 |
8 dremy 2019-10-27 21:00:15 +08:00 via iPhone 直接一个 for 循环+await 就能搞定的事,何必为了用函数式而为难自己呢…… |
![]() | 9 miniwade514 2019-10-28 08:19:14 +08:00 via iPhone 首先第一个 sleep 函数实现的就有问题,已经返回 promise 了为啥还要加 async ?感觉没有理解 async 的作用 |
![]() | 10 ericls 2019-10-28 08:43:16 +08:00 |
11 photon006 2019-10-28 09:51:56 +08:00 |
![]() | 12 weixiangzhe 2019-10-28 10:00:41 +08:00 via Android 还是 for 吧 |
![]() | 13 shaoyaoju OP @miniwade514 的确是多余了,谢谢提醒! |