遍历执行异步任务(forEach - await , for of - await , for-await-of , reduce)
javascript 异步 遍历
异步任务的遍历
业务中会碰到一些场景,需要遍历异步任务,同时希望它们依次执行,即任务一执行结束后再执行任务二,下面探讨一下几种实现方式的可行性和区别。
1.forEach + await
function getDatas(data) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(data);
}, 1000);
});
}
let arrays = [1, 2, 3];
arrays.forEach(async (item, index) => {
console.log(`第${index + 1}次开始`);
let datas = await getDatas(item);
console.log("返回结果:" + datas);
console.log(`第${index + 1}次结束`);
});
打印结果
第1次开始;
第2次开始;
第3次开始;
一秒后再次打印:
返回结果:1
第1次结束
返回结果:2
第2次结束
返回结果:3
第3次结束
可以发现几次执行结果是同时打印出来的,而不是打印1之后过一秒再打印2,所以这种方式不可行,同样的,map, some之类的方法也一样,基本上可以理解为:以回调处理的相关方法里,内部写异步都不能达成效果。
2. for 循环/for of + await
function getDatas(data) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(data);
}, 1000);
});
}
let arrays = [1, 2, 3];
async function exec() {
for (let index = 0; index < arrays.length; index++) {
let item = arrays[index];
console.log(`第${index + 1}次开始`);
let datas = await getDatas(item);
console.log("返回结果:" + datas);
console.log(`第${index + 1}次结束`);
}
}
exec();
打印结果
第1次开始;
一秒后再次打印:
返回结果:1
第1次结束
第2次开始
一秒后再次打印:
返回结果:2
第2次结束
第3次开始
一秒后再次打印:
返回结果:3
第3次结束
这就是我们想要实现的效果,说明for 循环 + await可行。
假设把中间for循环的代码改为for-of
async function exec() {
let index = 0;
for (let item of arrays) {
console.log(`第${index + 1}次开始`);
let datas = await getDatas(item);
console.log("返回结果:" + datas);
console.log(`第${index + 1}次结束`);
index++;
}
}
可以发现打印结果跟上面的for 循环 + await结果相同,说明for-of + await 也可行。
造成以上两种实现方式结果差异的原因是:forEach 是直接调用回调函数,for…of 是通过迭代器的方式去遍历。
3. for-await-of
function getDatas(times) {
times = times || 0;
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(times);
}, times * 1000);
});
}
let arrays = [getDatas(2), getDatas(3), getDatas(1)];
async function exec() {
for await (let item of arrays) {
console.log(item);
}
}
exec();
打印结果
2 秒后打印:
第1次开始
返回结果:2
第1次结束
一秒后再次打印:
第2次开始
返回结果:3
第2次结束
第3次开始
返回结果:1
第3次结束
把for-of + await的例子改为类似的形式:
function getDatas(times) {
times = times || 0;
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(times);
}, times * 1000);
});
}
let arrays = [getDatas(2), getDatas(3), getDatas(1)];
async function exec() {
let index = 0;
for (let item of arrays) {
console.log(`第${index + 1}次开始`);
let datas = await item;
console.log("返回结果:" + datas);
console.log(`第${index + 1}次结束`);
index++;
}
}
exec();
打印结果
立即打印:
第1次开始;
2 秒后再次打印:
返回结果:2
第1次结束
第2次开始
1 秒后再次打印:
返回结果:3
第2次结束
第3次开始
返回结果:1
第3次结束
通过打印结果可以看到,两者是有区别的,for-of + await 是因为代码块中有 await 导致等待 Promise 的状态而不再继续执行,而 for-await-of 是整个代码块都不执行,等待 arrays 中当前遍历到的值(Promise 状态)发生变化之后,才执行代码块的内容。
tip: 使用for-await-of时,如果某一次遍历中碰到了rejected状态的promise,后面的遍历代码块就不会执行了,前面的还是会正常执行。
4. reduce
除了以上几种方式,reduce方法也可以实现依次执行异步任务的效果,实现的方案是构造串行的异步任务。