forEach 同/异步问题

结论

先说结论:

  • forEach是异步执行
  • for-infor-of是同步执行

forEach 是异步执行的

借用网上一个例子:

我们写代码的时候,希望代码能按照arr数组的顺序执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function handle(x) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(x)
}, 1000 * x)
})
}

function test() {
let arr = [3, 2, 1]
arr.forEach(async item => {
const res = await handle(item)
console.log(res)
})
console.log('结束')
}

test()

我们更希望上边的实行结果为:

1
2
3
4
3
2
1
结束

但是,真实情况是

1
2
3
4
结束
1
2
3

实现一个 forEach

我们自己简单polyfill一个 forEach,用for循环实现(据说查mdn源代码是用while循环实现,不过都一样):

1
2
3
4
5
6
const myForEach = function(fn) {
let i
for(i=0; i<this.length; i++){
fn(this[i], i)
}
}

这里的fn函数就是我们例子中的 async/await 方法,forEach直接执行了fn(),而没有 await 就开始了下一次循环,或者换句话说,直接执行了Promise的构造函数部分代码,而promise.then代码没有被等待,就开始了下一次循环(执行下一个Promise构造函数的代码),而之前的promise.then都进入了事件队列,等循环结束后,再把所有事件队列里的任务拿出来执行,这就相当于同时发出去了好几个异步任务,哪个异步任务先执行完取决于自己执行多久。

比如,我们把数组的顺序let arr = [3, 2, 1],改为let arr = [3, 1, 2],再在构造函数里加一个console.log('执行了构造函数', x),这样就能清楚的看到上面的解释了,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function handle(x) {
return new Promise((resolve, reject) => {
console.log('执行了构造函数', x);
setTimeout(() => {
resolve(x)
}, 1000 * x)
})
}

function test() {
let arr = [3, 1, 2]
arr.forEach(async item => {
const res = await handle(item)
console.log(res)
})
console.log('结束')
}

test();

// 执行了构造函数 3
// 执行了构造函数 1
// 执行了构造函数 2
// 结束
// Promise {<fulfilled>: undefined}
// 1
// 2
// 3

看执行结果你就懂了

for of 是同步执行的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function handle(x) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(x)
}, 1000 * x)
})
}

async function test() {
let arr = [3, 2, 1]
for(const item of arr) {
const res = await handle(item)
console.log(res)
}
console.log('结束')
}

test()

结果:

1
2
3
4
3
2
1
结束

因为for...of并不像forEach那么简单粗暴的方式去遍历执行,而是采用一种特别的手段——迭代器 去遍历。

1
2
3
4
5
6
7
8
9
10
11
12
let arr = [3, 2, 1];
// 这就是迭代器
let iterator = arr[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

// {value: 3, done: false}
// {value: 2, done: false}
// {value: 1, done: false}
// {value: undefined, done: true}

好,现在我们把 iterator 用一到我们最开始的代码中;如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function handle(x) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(x)
}, 1000 * x)
})
}

async function test() {
let arr = [3, 2, 1]
let iterator = arr[Symbol.iterator]();
let res = iterator.next();
while(!res.done) {
let value = res.value;
console.log(value);
await handle(value);
res = iterator.next();
}
console.log('结束')
}
test()

打印一下结果:

1
2
3
4
// 3
// 2
// 1
// 结束

参考:

  1. forEach 和 for of 的执行异步顺序问题
  2. forEach 同/异步问题