主题
JS 循环中使用 async 与 await
1. for
循环 中使用 await
for
循环里面的异步操作,一般都是并发操作
若给for
循环里面发异步操作前加上await
关键字,则会变成串行操作,前提是for
应该在async
函数里面。
例如:
js
// 异步
let fn = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 1000);
});
};
async function fn1() {
console.log("Start");
let arr = [27, 0, 14];
for (var i = 0; i < arr.length; i++) {
await fn();
console.log(arr[i]);
}
console.log("End");
}
fn1();
这种行为适用于大多数循环(比如while
和for of
for in
循环)
2. 在 forEach
循环中使用 await
for
循环写起来很繁琐,是否很多时候会不经意想用forEach
来达到相同的效果,但是,真的可以吗?
错误示例:
js
let arr = [27, 0, 14];
console.log("Start");
arr.forEach(async (item) => {
await fn();
console.log(item);
});
console.log("End");
得到的结果不是想要的
JavaScript
中的 forEach
不支持 promise
感知,也支持 async
和await
,所以不能在 forEach
使用 await
。
3. 在 map
中使用 await
map
方法会返回一个由原数组每个元素执行回调函数的结果组成的新数组,如果在map中使用await
, map
始终返回promise
数组,这是因为异步函数总是返回promise
。并且李敏每个promise
都是pending
状态。
js
// 异步
let fn = (arg) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(arg);
}, 1000);
});
};
let arr = [27, 0, 14];
async function fn1() {
console.log("Start");
let resuPromises = arr.map(async (item) => {
let resu = await fn(item);
return resu;
});
console.log(resuPromises);
console.log("End");
}
fn1();
如果你在 map
中使用 await
,map
总是返回promises
,你必须等待promises
数组得到处理。 或者通过await Promise.all()
来完成此操作。
js
// 异步
let fn = (arg) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(arg);
}, 1000);
});
};
let arr = [27, 0, 14];
async function fn1() {
console.log("Start");
let resuPromises = arr.map(async (item) => {
let resu = await fn(item);
return resu;
});
let res = await Promise.all(resuPromises);
console.log(res);
console.log("End");
}
fn1();
4. 在 filter
循环中使用 await
当你使用filter时,希望筛选具有特定结果的数组。假设过滤数量大于20的数组。
如果正常使用filter
(没有 异步),如下:
js
let arr = [27, 0, 14];
function fn1() {
console.log("Start");
let resuPromises = arr.filter((item) => item > 20);
console.log(resuPromises);
console.log("End");
}
fn1();
// 输出:
Start
[ 27 ]
End
但是,若存在异步,filter
中的await
不会以相同的方式工作。 事实上,它根本不起作用。
错误示例:
js
// 异步
let fn = (arg) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(arg);
}, 1000);
});
};
let arr = [27, 0, 14];
function fn1() {
console.log("Start");
let resuPromises = arr.filter(async (item) => {
let res = await fn(item)
return res > 20
});
console.log(resuPromises);
console.log("End");
}
fn1();
// 输出:
Start
[ 27, 0, 14 ]
End
当在filter
回调中使用await
时,回调总是一个promise
。由于promise
总是真的,数组中的所有项都通过filter
。在filter
使用 await
类以下这段代码
js
const filtered = arr.filter(true);
在filter
使用 await
正确的三个步骤
- 使用
map
返回一个promise
结果数组 - 使用
await + Promise.all
取得处理结果 - 使用
filter
对返回的结果进行处理
5. 在 reduce
循环中使用 await
正常情况下,reduce
的使用如下
js
let arr = [27, 0, 14];
function fn1() {
console.log("Start");
let count = arr.reduce((pre, cur) => {
return (pre += cur);
}, 0);
console.log(count);
console.log("End");
}
fn1();
// 输出
Start
41
End
但是,当reduce
里面有异步操作,使用await
会让结果出乎意料。
js
// 异步
let fn = (arg) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(arg);
}, 1000);
});
};
let arr = [27, 0, 14];
async function fn1() {
console.log("Start");
let count = await arr.reduce(async (pre, cur) => {
let sum = await fn(cur);
pre = pre + sum;
return pre
}, 0);
console.log(count);
console.log("End");
}
fn1();
注意:reduce
有返回值,若reduce
内部是异步函数,异步函数的返回值是promise
,要取到promise
的值,需要在reduce
前也加上await
,或通过then
来取值。
可以看到返回的值是[object Promise]14
,来分析一下原因,来打印一下内部pre
和sum
的值看。 即,在reduce
里面加上一行console.log
: console.log("sum--->:", sum, "pre--->:", pre);
js
let arr = [27, 0, 14];
async function fn1() {
console.log("Start");
let count = await arr.reduce(async (pre, cur) => {
let sum = await fn(cur);
console.log("sum--->:", sum, "pre--->:", pre);
pre = pre + sum;
return pre;
}, 0);
console.log(count);
console.log("End");
}
fn1();
// 输出
Start
sum--->: 27 pre--->: 0
sum--->: 0 pre--->: Promise { 27 }
sum--->: 14 pre--->: Promise { '[object Promise]0' }
[object Promise]14
End
原因一目了然。改造一下。等待一下pre
,等cur
和pre
返回后再累计加。
js
let arr = [27, 0, 14];
async function fn1() {
console.log("Start");
let count = await arr.reduce(async (pre, cur) => {
let p = await pre;
let sum = await fn(cur);
return p + sum;
}, 0);
console.log(count);
console.log("End");
}
fn1();
输出结果:
但是,可以看到reduce
计算的时间有段长,因为每一次计算都需要先等待pre
返回,时间累加起来就会很慢。
换种思路,先等待每个获取当前项值的fn
异步函数返回,因为此时相当于并发操作,再等待pre
,此时等待pre
其实只是从padding
中的promise
里面获取已有的结果,等待时间可以忽略,因此并发的时间可以看成一个fn
函数的返回时间,也就是一秒。
正确示例:
js
// 异步
let fn = (arg) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(arg);
}, 1000);
});
};
let arr = [27, 0, 14];
async function fn1() {
console.log("Start");
let count = await arr.reduce(async (pre, cur) => {
let sum = await fn(cur); // 先等待cur
let p = await pre; // 再等待pre
return p + sum;
}, 0);
console.log(count);
console.log("End");
}
fn1();
因此,在reduce
里面使用await
时候,等待的顺序必须注意
在reduce中使用wait最简单(也是最有效)的方法是
- 使用map返回一个promise 数组
- 使用 await promise.all 等待处理结果
- 使用 reduce 对返回的结果进行处理(同步)
总结
- 如果你想连续执行
await
调用,请使用for循环
(或任何没有回调的循环)。 - 永远不要和
forEach
一起使用await
,而是使用for循环
(或任何没有回调的循环)。 - 不要在
filter
和reduce
中使用awai
t,如果需要,先用map
进一步骤处理,然后在使用filter
和reduce
进行处理。