当foreach遇上asyn cawait
问题描述
var getNumbers = () => {
return Promise.resolve([1, 2, 3])
}
var multi = num => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (num) {
resolve(num * num)
} else {
reject(new Error('num not specified'))
}
}, 1000)
})
}
async function test () {
var nums = await getNumbers()
nums.forEach(async x => {
var res = await multi(x)
console.log(res)
})
}
test()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
代码执行的结果是:1 秒后,一次性输出1,4,9。这个结果和我们的预期有些区别,我们是希望每间隔 1 秒,然后依次输出 1,4,9;所以当前代码应该是并行执行了,而我们期望的应该是串行执行。
解决方式一
我们可以改造一下 forEach,确保每一个异步的回调执行完成后,才执行下一个
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array)
}
}
async function test () {
var nums = await getNumbers()
asyncForEach(nums, async x => {
var res = await multi(x)
console.log(res)
})
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
解决方式二
使用 for-of 替代 for-each
async function test () {
var nums = await getNumbers()
for(let x of nums) {
var res = await multi(x)
console.log(res)
}
}
1
2
3
4
5
6
7
2
3
4
5
6
7
解释:
for-of 可以遍历各种集合对象的属性值,要求被遍历的对象需要实现迭代器 (iterator) 方法,例如 myObjectSymbol.iterator 用于告知 JS 引擎如何遍历该对象。一个拥有 Symbol.iterator 方法的对象被认为是可遍历的。
var zeroesForeverIterator = {
[Symbol.iterator]: function () {
return this;
},
next: function () {
return {done: false, value: 0};
}
};
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
如上就是一个最简单的迭代器对象;for-of 遍历对象时,先调用遍历对象的迭代器方法 Symbol.iterator,该方法返回一个迭代器对象(迭代器对象中包含一个 next 方法);然后调用该迭代器对象上的 next 方法。
每次调用 next 方法都返回一个对象,其中 done 和 value 属性用来表示遍历是否结束和当前遍历的属性值,当 done 的值为 true 时,遍历就停止了。
for (VAR of ITERABLE) {
STATEMENTS
}
1
2
3
2
3
等价于:
var $iterator = ITERABLE[Symbol.iterator]();
var $result = $iterator.next();
while (!$result.done) {
VAR = $result.value;
STATEMENTS
$result = $iterator.next();
}
1
2
3
4
5
6
7
2
3
4
5
6
7
由此可以知道 for-of 和 forEach 遍历元素时处理的方式是不同的。