在 Map 遍历中使用 async 函数 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
shaoyaoju
V2EX    程序员

在 Map 遍历中使用 async 函数

 
  •   shaoyaoju
    juzhiyuan 2019-10-27 11:36:33 +08:00 4235 次点击
    这是一个创建于 2178 天前的主题,其中的信息可能已经有所发展或是发生改变。

    原文 https://blog.shaoyaoju.org/Javascript-async-with-map/

    有时需要使用 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] 

    参考

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

    本篇文章由一文多发平台ArtiPub自动发布

    13 条回复    2019-10-28 11:31:50 +08:00
    popn74
        1
    popn74  
       2019-10-27 13:05:51 +08:00
    添加 promise 数组
    然后 promise.all
    secondwtq
        2
    secondwtq  
       2019-10-27 13:07:13 +08:00 via iPhone
    恭喜你重新实现了 mapM 的某个实例
    momocraft
        3
    momocraft  
       2019-10-27 13:13:00 +08:00
    div class="reply_content">map 的语义其实就更接近并行,需要等的用 reduce 更自然些
    yyfearth
        4
    yyfearth  
       2019-10-27 14:15:47 +08:00   1
    @shaoyaoju 这么麻烦 要等待用 for of + await 就是了 非要用 forEach/map 或者 reduce 干嘛
    并行用 Promise.all + array.map 就是
    sam014
        5
    sam014  
       2019-10-27 14:40:44 +08:00
    我们期望的是,在每一次循环时,暂停约 3s 钟时间后再继续执行;
    ---------------------------------------------------------------------------------------
    这种情况用 生成器 应该好做一些
    lqzhgood
        6
    lqzhgood  
       2019-10-27 17:58:03 +08:00 via Android
    foreach 是并行 for 是串行
    理论上不是 foreach 更快么…
    我怎么记得 for 更快

    等下起来验证一下

    const a = new Array(10000);
    terax
        7
    terax  
       2019-10-27 18:31:46 +08:00 via iPhone
    据我的理解,js async await 并不是阻塞代码的执行(只是 promise 语法糖,并不会改变 runtime )。可以理解为 await 之后的代码都被包在了 promise.then 里面了。
    dremy
        8
    dremy  
       2019-10-27 21:00:15 +08:00 via iPhone
    直接一个 for 循环+await 就能搞定的事,何必为了用函数式而为难自己呢……
    miniwade514
        9
    miniwade514  
       2019-10-28 08:19:14 +08:00 via iPhone
    首先第一个 sleep 函数实现的就有问题,已经返回 promise 了为啥还要加 async ?感觉没有理解 async 的作用
    ericls
        10
    ericls  
       2019-10-28 08:43:16 +08:00
    weixiangzhe
        12
    weixiangzhe  
       2019-10-28 10:00:41 +08:00 via Android
    还是 for 吧
    shaoyaoju
        13
    shaoyaoju  
    OP
       2019-10-28 11:31:50 +08:00
    @miniwade514 的确是多余了,谢谢提醒!
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1256 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 23:41 PVG 07:41 LAX 16:41 JFK 19:41
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86