forEach 如果传入异步回调如何保证并行执行?

简介: forEach 本身是同步的,但是如果回调函数是异步的,那么forEach 会立即执行下一个任务,而不会等待回调函数执行完毕,这个时候如何保证异步任务的串行执行呢?

forEach本身是同步的,但是如果回调函数是异步的,那么forEach会立即执行下一个任务,而不会等待回调函数执行完毕,这个时候如何保证异步任务的串行执行呢?

这是我之前写的一篇文章中提到的问题,文章地址:别再恶搞forEach了,它就是单纯的从头遍历到尾,它没有那么多问题

可恶,没有一个人回答我这个问题,所以我自己分享出来好了。

异步

提到这个问题就要先说一下异步,异步是指程序的执行顺序与代码的书写顺序不一致,比如:

console.log(1)
setTimeout(() => {
   
  console.log(2)
}, 0)
console.log(3)

这段代码的执行顺序是132,而不是123,这就是异步。

异步指的不是回调函数,回调函数是一种异步的实现方式,但是并不是所有的异步都是回调函数,比如PromiseGeneratorAsync/Await等都是异步的实现方式,但是并不是回调函数。

串行

串行是指多个任务按照顺序执行,比如:

console.log(1)
console.log(2)
console.log(3)

这段代码的执行顺序是123,这就是串行。

串行执行异步任务

那么如何保证异步任务的串行执行呢?

1. 递归

const arr = [1, 2, 3]
const asyncForEach = (arr, callback) => {
   
  const loop = (index) => {
   
    if (index === arr.length) return
    callback(arr[index], index, arr, () => {
   
      loop(index + 1)
    })
  }
  loop(0)
}

asyncForEach(arr, (item, index, arr, next) => {
   
  setTimeout(() => {
   
    console.log(item)
    next()
  }, parseInt(Math.random() * 1000))
})

这里使用了递归,每次执行完一个任务后,调用next函数,next函数会执行下一个任务。

2. Promise

const arr = [1, 2, 3]
const asyncForEach = (arr, callback) => {
   
  arr.reduce((prev, cur, index) => {
   
    return prev.then(() => {
   
      return callback(cur, index, arr)
    })
  }, Promise.resolve())
}

asyncForEach(arr, (item, index, arr) => {
   
  return new Promise((resolve) => {
   
    setTimeout(() => {
   
      console.log(item)
      resolve()
    }, parseInt(Math.random() * 1000))
  })
})

这里使用了Promise,每次执行完一个任务后,返回一个PromisePromiseresolve函数会执行下一个任务。

3. Async/Await

const arr = [1, 2, 3]
const asyncForEach = async (arr, callback) => {
   
  for (let i = 0; i < arr.length; i++) {
   
    await callback(arr[i], i, arr)
  }
}

asyncForEach(arr, async (item, index, arr) => {
   
  return new Promise((resolve) => {
   
    setTimeout(() => {
   
      console.log(item)
      resolve()
    }, parseInt(Math.random() * 1000))
  })
})

这里使用了Async/Await,每次执行完一个任务后,使用await等待下一个任务执行完毕。

forEach 中的异步如何实现串行?

上面说了那么多示例,那么forEach中的异步如何实现串行呢?

const arr = [1, 2, 3]
arr.forEach(async (item) => {
   
  setTimeout(() => {
   
    console.log(item)
  }, parseInt(Math.random() * 1000))
})

上面的代码中,forEach中的输出是随机的,你并不知道如何保证输出的顺序,那么如何保证输出的顺序呢?

1. 递归

这个时候有人就问了,forEach你怎么递归?来看看forEach递归的实现:

const arr = [1, 2, 3]
const asyncForEach = (arr) => {
   
   arr.forEach(async (item, index, arr) => {
   
      const newArr = arr.splice(index + 1, arr.length);
      setTimeout(() => {
   
         console.log(item)
         asyncForEach(newArr)
      }, parseInt(Math.random() * 1000))
   })
}
asyncForEach(arr)

这里的原理就是使用splice截取数组,这样就会修改原数组,导致forEach的每次只会迭代一次,但是缺点就是index不会发生变化,同时原数组也会发生变化。

后面两个没必要看了,就是凑字数的,和上面描述的差不多,也没用到forEach,障眼法而已。

2. Promise

const arr = [1, 2, 3]
const asyncForEach = (arr) => {
   
  arr.reduce((prev, cur, index) => {
   
    return prev.then(() => {
   
      return new Promise((resolve) => {
   
        setTimeout(() => {
   
          console.log(cur)
          resolve()
        }, parseInt(Math.random() * 1000))
      })
    })
  }, Promise.resolve())
}

asyncForEach(arr)

这里没有使用forEach,而是使用了reduce,这样就可以保证index不会发生变化,同时原数组也不会发生变化。

reduce会收集每次的Promise,最后返回一个Promise,这样就可以保证每次执行完一个任务后,再执行下一个任务。

3. Async/Await

const arr = [1, 2, 3]
const asyncForEach = async (arr) => {
   
  for (let i = 0; i < arr.length; i++) {
   
    await new Promise((resolve) => {
   
      setTimeout(() => {
   
        console.log(arr[i])
        resolve()
      }, parseInt(Math.random() * 1000))
    })
  }
}

asyncForEach(arr)

这里也没使用forEach,而是使用了for循环,这样就可以保证index不会发生变化,同时原数组也不会发生变化。

总结

异步真好玩,玩的好升职加薪,玩不好提桶走人,今天的分享就到了,不会真的有人在forEach中使用异步回调吧???

目录
相关文章
|
8月前
|
编译器 数据处理 C#
C#中的异步流:使用IAsyncEnumerable<T>和await foreach实现异步数据迭代
【1月更文挑战第10天】本文介绍了C#中异步流的概念,并通过使用IAsyncEnumerable<T>接口和await foreach语句,详细阐述了如何异步地迭代数据流。异步流为处理大量数据或需要流式处理数据的场景提供了一种高效且非阻塞性的方法,使得开发者能够更优雅地处理并发和数据流问题。
|
前端开发 JavaScript
😲完了完了,forEach异步执行,怎么后面的先完成了!?
代码review,业务里的代码千奇百怪,到底还能遇到什么呢?oh no,真的有人在forEach里用异步调用!
554 0
|
前端开发
异步转同步的几种方法
在循环等待中,我们可以使用一个变量来指示异步操作是否已完成。然后,我们可以在循环中检查该变量,如果它指示异步操作已完成,则退出循环。
581 0
|
2月前
|
前端开发 JavaScript
如何使用 Promise 处理异步并发操作?
通过使用 `Promise.all()` 和 `Promise.race()` 方法,可以灵活地处理各种异步并发操作,根据不同的业务需求选择合适的方法来提高代码的性能和效率,同时也使异步代码的逻辑更加清晰和易于维护。
|
8月前
|
JavaScript Java Spring
@Async异步失效的9种场景
在Spring中,启用@Async异步功能需要在启动类或配置类上使用`@EnableAsync`。若未使用此注解,@Async将无效。另外,内部方法调用(如在一个类的方法中调用另一个被@Async注解的方法)会导致异步功能失效,因为这不涉及Spring的AOP代理。此外,@Async方法必须是public,返回类型为void或Future,不能是static或final,且其所在的类需被@Service等注解以使Spring管理。如果使用@ComponentScan,确保正确扫描包含@Async类的包路径。
120 1
|
3月前
|
前端开发 JavaScript
Async/Await 如何通过同步的方式(形式)实现异步
Async/Await 是一种在 JavaScript 中以同步方式书写异步代码的语法糖。它基于 Promise,使异步操作看起来更像顺序执行,简化了回调地狱,提高了代码可读性和维护性。
|
4月前
|
存储 前端开发 JavaScript
node中循环异步的问题[‘解决方案‘]_源于map循环和for循环对异步事件配合async、await的支持
本文探讨了在Node.js中处理循环异步操作的问题,比较了使用map和for循环结合async/await处理异步事件的差异,并提供了解决方案。
51 0
|
5月前
|
前端开发 JavaScript Java
java实现异步回调返回给前端
综上,Java中实现异步回调并将结果返回给前端是一项涉及后端异步处理和前端交互的综合任务。在实际项目中,开发人员需要根据应用需求和性能预期选择合适的异步模型与工具,并进行适当的配置和优化。
262 3
|
6月前
|
Web App开发 JavaScript 前端开发
谁说forEach不支持异步代码,只是你拿不到异步结果而已
JavaScript 的 `forEach` 不直接支持异步操作,但可以在回调中使用 `async/await`。虽然 `forEach` 不会等待 `await`,异步代码仍会执行。MDN 文档指出 `forEach` 预期同步回调。ECMAScript 规范和 V8 源码显示 `forEach` 基于 for 循环实现,不返回 Promise。通过 `setTimeout` 可观察到异步操作完成。与 `map` 不同,`forEach` 不适合处理异步序列,常需转换为 `Promise.all` 结合 `map` 的方式。
60 11
3 # 通过回调函数处理异步并发问题
3 # 通过回调函数处理异步并发问题
56 0

热门文章

最新文章

下一篇
开通oss服务