ForEach不支持async/await?怎么处理?

一、问题场景

function showData(data) {
    // data是个数组 里面的值需要转换后再进行展示
    data.forEach(async t => 
        {const a = await myformatter(t);
         console.log(a)}
    );
    console.log('全部转换完毕')
}
const myformatter = (t)=> new Promise(reslove=> {
    setTimeout(() => reslove(t + 1),100)
})
const data = [0,1,2]
showData(data)

这么写,我们预期的结果是:

1

2

3

全部转换完毕

但是实际结果是:

 全部转换完毕

1

2

3

二、原因

forEach的实现大概可以抽象为

Array.prototype.forEach = function (callback) {
  for (let index = 0; index < this.length; index++) {
    callback(this[index], index, this);
  }
};

从上述代码中我们可以发现,forEach 只是简单的执行了下回调函数而已,并不会去处理异步的情况。并且你在 callback 中即使使用 break 也并不能结束遍历。

所以上诉代码的实际执行顺序是:

const data = [0,1,2];
data.forEach(....)

=>

首先会遇到 `const a = await myformatter(t)语句`
由于await语法是从右到左执行
因此它会执行 new Promise(reslove => setTimeout(() => reslove(t+1),100))
同时
由于async函数始终会返回一个promise对象,即'return Promise.reslove(new Promise....)' ? 错了,因为myformatter返回了promise对象,那么async并不会再嵌套一个promise对象,则最终返回 return Promise.reslove(setTimeout(t+1,100))

由于await的原因,它其实相当于执行了Promise.reslove(setTimeout(t+1,100)).then() 方法。这是个微任务,会被hold住并放入事件栈的后面,等待本次同步任务执行完之后,才被执行

而JS引擎主线流程并不会闲置,它会继续执行同步任务,同时继续吧下一个t的return Promise.reslove(setTimeout(t+1,100))放到微服务。直到所有同步任务执行完毕

等待console.log('全部转换完毕')执行完毕后,才开始执行微任务。

相关面试题:

let sum = 0
const arr = [1, 2, 3]

async function sumFn(a, b) {
  return a + b
}

function main(array) {
  array.forEach(async item => {
    sum = await sumFn(sum, item)
    console.log(sum);
  })

  console.log(sum);
}

main(arr)

输出结果

0

1

2

3

 首先forEach之后的console.log(sum)被执行,此时forEach纷发的三个微任务仍未执行,则输出0

其次执行微任务

await sumFn(0,1)

await sumFn(0,2)

await sumFn(0,3)

三、解决方法

1、for of

let sum = 0
const arr = [1, 2, 3]

async function sumFn(a, b) {
  return a + b
}

// await 要放在 async 函数中
async function main(array) {
  for (let item of array) {
    sum = await sumFn(sum, item)
    console.log(sum)
  }

  console.log(sum) // 6
}

main(arr)

1
3
6
6

原因:for of 本质上是while循环

上面的代码等同于:

let sum = 0
const arr = [1, 2, 3]

async function sumFn(a, b) {
  return a + b
}

// await 要放在 async 函数中
async function main(array) {
  // for (let item of array) {
  //   sum = await sumFn(sum, item)
  // }

  // 相当于
  const iterator = array[Symbol.iterator]()
  let iteratorResult = iterator.next()
  while (!iteratorResult.done) {
    sum = await sumFn(sum, iteratorResult.value)
    console.log(sum)
    iteratorResult = iterator.next()
  }

  console.log(sum) // 6
}

main(arr)

2、forEach可以用map代替(为什么用map,因为forEach无返回值,map能把await/async生成的promise对象返回到promise.all里),利用Promise.all进行等待。

请注意 map+Promise.all无法解决上面这个sum a+b的问题。因为只有promise.all是同步的,它接受了几个promise微任务,而微任务生成的时候,sum是一直没有被改变的(因为promise是微任务)。

但是map+promise可以解决问题场景里的那个问题

async function showData(data) {
  // data是个数组 里面的值需要转换后再进行展示
  await Promise.all(data.map(async t => 
      {const a = await myformatter(t);
       console.log(a)}
  ))
  console.log('全部转换完毕')
}
const myformatter = (t)=> new Promise(reslove=> {
  setTimeout(() => reslove(t + 1),100)
})
const data = [0,1,2]
showData(data)

输出:

1

2

3

全部转换完毕

保证了data数据转换完毕后再往下走。

但是有一个缺陷,promise.all并不能保证promise完成顺序,如果要做按序累加的话,就不可以用promise.all了,只能用for of来保证顺序

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值