本文详解JavaScript中异步任务的三种执行模式。通过Promise.all实现高效并行,利用async/await结合for循环实现严格串行,并介绍Promise.allSettled的容错处理。针对高频接口调用限制场景,提供并行与串行结合的批量处理方案,平衡执行效率与系统稳定性。
在开发过程中,控制多个异步任务的执行顺序是常见需求。本文将梳理如何实现多个异步任务的并行执行、串行执行,以及将两者结合使用的具体方法。
一、并行
并行是应用最广泛的执行方式。对于多个相互之间没有依赖关系的异步任务,采用并行执行可以显著提升整体效率。
开发中最常用的并行方案是Promise.all()。
function f1() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('1结束');
resolve();
}, 1000)
});
}
function f2() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('2结束');
resolve();
}, 900)
});
}
function f3() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('3结束');
resolve();
}, 800)
});
}
let arr = [f1, f2, f3];
Promise.all(arr.map(i => i()));
// 3结束
// 2结束
// 1结束除了Promise.all(),以下几种数组遍历方式同样可以实现并行效果。
// forEach遍历
arr.forEach(item => {
item();
});
// for循环
for (let i = 0; i < arr.length; i++) {
arr[i]();
}
// for...of遍历
for (let item of arr) {
item();
}
// 注意,以下两种写法同样是并行的
arr.forEach(async item => await item());
async function f() {
arr.forEach(async item => await item())
}
f();相比之下,Promise.all()能够确保所有任务都执行成功后,再执行后续操作,这是普通遍历方法无法做到的。
此外,还有一种方式也能实现并行:Promise.allSettled()。
Promise.allSettled(arr.map(i => i()));这种方式较为特殊,它无法直接获取每个Promise对象的返回值,但可以精确获知每个任务的成功或失败状态。如果有此类需求场景,使用Promise.allSettled()非常合适。
二、串行
我在工作中曾遇到一个场景:有一个包含1000多个元素的数组,每个成员都是调用第三方接口的Promise对象。我像往常一样使用Promise.all(),期望这1000多个任务能瞬间完成。然而结果令人失望,这1000多个任务中只有一部分成功,大部分都报错了。无论执行多少次,结果始终如此。经过一番排查,我从第三方接口方得知,他们的接口存在调用限制,同一时间只能并行处理300个请求。
有没有办法让它们一个接一个地执行呢?也就是串行执行。
Node.js Koa框架中的next()语法给了我启发,它的作用是让中间件一个接一个地执行。基于此,我想到了使用递归的方式来实现串行:
async function serial(arr) {
let item = arr.shift();
await item();
if (arr.length > 0) {
await serial(arr);
}
}
serial(arr);
// 1结束
// 2结束
// 3结束其实,想让异步任务串行,并不需要这么复杂。以下遍历方式同样可以实现串行执行。
// 使用for...of
async function f() {
for (let item of arr) {
await item();
}
}
f();
// 使用for循环
async function f() {
for (let i = 0; i < arr.length; i++) {
await arr[i]();
}
}
f();注意观察,为什么同样是for循环,同样是for...of,前面的写法是并行,后面却变成了串行?
在工作中,我们一定做过这样的尝试:想通过遍历让多个异步任务串行执行。但往往不得其法,无论怎么调整,它们仍然是同时执行的。
后一种写法可以理解为:只有当await执行完成后,才会进入下一次循环。实际上,遍历过程相当于把每一个元素在代码中从上到下依次书写。当这些元素处于async函数中,并在每个元素前面加上await时,它们自然就能顺序执行。否则,正如我们所知,简单的顺序书写的异步任务,它们仍然是同时执行的。
现在程序不再报错了。但是,1000多个任务依次执行完成,足足花了十多分钟,速度太慢!有没有办法既能保持速度,又不触发接口调用限制呢?
有办法。如果可以并行处理200个任务,完成后再开始下一轮200个……也就是,将并行和串行相结合。
三、并行串行结合
async function bingChuan(arr, num) {
let items = arr.splice(0, num);
await Promise.all(items.map(i => i()));
if (arr.length > 0) {
await bingChuan(arr, num);
}
}
bingChuan(arr, 2);
// 2结束
// 1结束
// 3结束现在,我们可以同时享有并行和串行的优势了!
到此这篇关于JS异步任务的并行、串行,以及二者结合的文章就介绍到这了,更多相关js异步任务并行内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!