forEach
本身是同步的,但是如果回调函数是异步的,那么forEach
会立即执行下一个任务,而不会等待回调函数执行完毕,这个时候如何保证异步任务的串行执行呢?
这是我之前写的一篇文章中提到的问题,文章地址:别再恶搞forEach了,它就是单纯的从头遍历到尾,它没有那么多问题
可恶,没有一个人回答我这个问题,所以我自己分享出来好了。
异步
提到这个问题就要先说一下异步,异步是指程序的执行顺序与代码的书写顺序不一致,比如:
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
console.log(3)
这段代码的执行顺序是1
、3
、2
,而不是1
、2
、3
,这就是异步。
异步指的不是回调函数,回调函数是一种异步的实现方式,但是并不是所有的异步都是回调函数,比如Promise
、Generator
、Async/Await
等都是异步的实现方式,但是并不是回调函数。
串行
串行是指多个任务按照顺序执行,比如:
console.log(1)
console.log(2)
console.log(3)
这段代码的执行顺序是1
、2
、3
,这就是串行。
串行执行异步任务
那么如何保证异步任务的串行执行呢?
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
,每次执行完一个任务后,返回一个Promise
,Promise
的resolve
函数会执行下一个任务。
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
中使用异步回调吧???