JS是单线程在主线程中执行,同步任务是按照步骤一步一步执行,但是遇到异步任务会打乱程序执行顺序,如果出现复杂场景就会遇到多个异步任务的串行、并行、混合执行、批量控制等情况,下面就来看下不同的情况是实现思路。
1.通用的数据结构
需要用一个队列存储js的任务,把任务先收集起来
let queue = [];
queue.push(task);
同步执行(按顺序执行)
多个异步任务按顺序依次执行,前一个任务完成后再开始下一个。
实现方式:
Promise.then链式调用async/await同步风格写法
其实就是遍历这个队列,可以用循环或者递归的方式。
// forEach
Promise.resolve().then(() => {
let sequence = Promise.resolve()
queue.forEach(item => {
sequence = sequence.then(item)
})
})
//reduce
queue.reduce((acc, item) => acc.then(()=>{
return item();
}), Promise.resolve()); // 串行执行
// 递归(koa洋葱模型)
const dispatch = async (i) => {
if (i < this.middlewares.length) {
await Promise.resolve(queue[i](ctx, () => dispatch(i + 1)));
}
};
// 使用for...of
async function f() {
for (let item of arr) {
await item();
}
}
// 使用for循环
async function f() {
for (let i = 0; i < arr.length; i++) {
await arr[i]();
}
}
// 递归
async function serial(arr) {
let item = arr.shift();
await item();
if (arr.length > 0) {
await serial(arr);
}
}
2.并行执行(同时执行)
多个异步任务同时启动,等待所有任务完成后再进行下一步处理。
实现方式:
Promise.all:所有任务成功完成才返回,任何一个失败则立即失败Promise.allSettled:等待所有任务完成(无论成功或失败),返回所有结果
// 模拟异步任务(可能成功或失败)
function asyncTask(name, delay = 1000, shouldFail = false) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldFail) {
console.log(`任务 ${name} 失败`);
reject(new Error(`任务 ${name} 执行失败`));
} else {
console.log(`任务 ${name} 完成`);
resolve(`结果:${name}`);
}
}, delay);
});
}
// 方式1:Promise.all - 全成功才成功,有一个失败则整体失败
async function runAllTasks() {
try {
const [resultA, resultB, resultC] = await Promise.all([
asyncTask('A', 1000),
asyncTask('B', 1500),
asyncTask('C', 800)
]);
console.log('所有任务成功完成:', resultA, resultB, resultC);
} catch (error) {
console.log('有任务失败:', error.message);
}
}
// 方式2:Promise.allSettled - 等待所有任务完成,无论成功失败
async function runAllSettledTasks() {
const results = await Promise.allSettled([
asyncTask('A', 1000),
asyncTask('B', 1500, true), // 这个任务会失败
asyncTask('C', 800)
]);
// 处理结果
const successes = [];
const failures = [];
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
successes.push({ task: index, value: result.value });
} else {
failures.push({ task: index, reason: result.reason.message });
}
});
console.log('成功的任务:', successes);
console.log('失败的任务:', failures);
}
3. 竞争执行(取最快完成的任务)
多个异步任务同时启动,只需要最快完成的那个任务的结果。
// 模拟不同延迟的异步任务
function fetchServer(name, delay) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`${name}`);
resolve(`${name}`);
}, delay);
});
}
// 模拟超时任务
function timeoutTask(ms) {
return new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`超过 ${ms}ms 未响应`));
}, ms);
});
}
// 场景1:从多个服务器获取数据,使用最快响应的结果
async function getFastestData() {
try {
const result = await Promise.race([
fetchServer('1', 1000),
fetchServer('2', 800), // 这个会最先完成
fetchServer('3', 1200)
]);
console.log(result);
} catch (error) {
console.log('错误:', error.message);
}
}
// 场景2:为异步任务设置超时限制
async function fetchWithTimeout() {
try {
const result = await Promise.race([
fetchServer('1', 1500),
timeoutTask(1000) // 1秒超时
]);
console.log('获取数据成功:', result);
} catch (error) {
console.log('获取数据失败:', error.message); // 会触发超时错误
}
}
// getFastestData();
// fetchWithTimeout();
实现方式:
Promise.race:返回第一个完成(无论成功或失败)的任务结果
4. 复杂依赖编排(混合串行与并行)
实际业务中常遇到更复杂的依赖关系,需要结合串行和并行方式。
// 分成多批,一批一批执行
async function fnBatch(arr, num) {
let items = arr.splice(0, num);
await Promise.all(items.map(i => i()));
if (arr.length > 0) {
await fnBatch(arr, num);
}
}
fnBatch(arr, 2);
// 按照最大请求数同时请求,完成一个加入一个新的
function promiseAllLimit(maxNums, arr) {
if (!arr.length) {
return;
}
let k = 0;
let result = [];
return new Promise((resolve, reject) => {
function next() {
if (k === arr.length) {
return;
}
let i = k++;
arr[i]()
.then((res) => {
result[i] = res;
})
.catch((err) => {
result[i] = err;
})
.finally(() => {
if (i < arr.length) {
next();
} else {
resolve(result);
}
});
}
while (k < maxNums) {
next();
}
});
}
const nums = new Array(100).fill(1).map((num, i) => i + 1);
const asyncDouble = (num) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve(num * 2);
}, 200);
});
};
console.log(
nums.map((num) => {
return () => asyncDouble(num);
})
);
const start = Date.now();
promiseAllLimit(
2,
nums.map((num) => {
return () => asyncDouble(num);
})
).then((r) => {
console.log("res", r);
const end = Date.now();
console.log(end - start);
});
5. 使用工具库简化编排(如 async.js)
-
特点:最经典的异步流程控制库,提供了丰富的串行、并行、限流等操作 API
-
适用场景:复杂的异步任务编排、批量操作控制
-
核心 API:
async.series:串行执行async.parallel:并行执行async.waterfall:带参数传递的串行执行async.mapLimit:限制并发数的批量处理
// 注意:需先安装async库(npm install async)
const async = require('async');
// 模拟异步任务
function task(name, delay, callback) {
setTimeout(() => {
console.log(`任务 ${name} 完成`);
callback(null, name); // 第一个参数为错误,第二个为结果
}, delay);
}
// 1. 串行执行(series)
async.series([
(callback) => task('A', 1000, callback),
(callback) => task('B', 800, callback),
(callback) => task('C', 1200, callback)
], (err, results) => {
if (err) console.error('串行任务失败:', err);
else console.log('串行任务结果:', results);
});
// 2. 并行执行(parallel)
async.parallel([
(callback) => task('X', 1000, callback),
(callback) => task('Y', 800, callback),
(callback) => task('Z', 1200, callback)
], (err, results) => {
if (err) console.error('并行任务失败:', err);
else console.log('并行任务结果:', results);
});
// 3. 限速并行(limit):同时最多执行2个任务
async.parallelLimit([
(callback) => task('L1', 1000, callback),
(callback) => task('L2', 800, callback),
(callback) => task('L3', 1200, callback),
(callback) => task('L4', 600, callback)
], 2, (err, results) => {
if (err) console.error('限速任务失败:', err);
else console.log('限速任务结果:', results);
});
总结
JavaScript 异步任务编排的核心是根据业务需求选择合适的执行策略:
- 串行执行:适合有依赖关系的任务(
async/await最直观) - 并行执行:适合无依赖关系、需要提高效率的任务(
Promise.all/Promise.allSettled) - 竞争执行:适合需要最快结果或超时控制的场景(
Promise.race) - 复杂依赖:结合串行和并行,用
async/await+Promise.all组合实现 - 超复杂场景:借助工具库(如
async.js)提供的高级 API
合理的异步任务编排能显著提高程序效率,减少不必要的等待时间,同时让代码逻辑更清晰易懂。