在 JavaScript 中,顺序执行多个异步任务(如接口请求、文件读取、定时器等)的核心是:前一个任务完成后,再执行下一个任务。以下是 4 种常用方案,按 “简洁度 + 实用性” 排序:
一、最推荐:async/await(清晰直观,现代首选)
async/await 是 ES8 语法,本质是 Promise 的语法糖,能把异步代码写得像同步代码一样直观,是顺序执行异步任务的最优方案。
核心逻辑:
- 用
async声明异步函数。 - 用
await等待每个异步任务完成,再执行下一个。
示例(10 个异步任务顺序执行):
// 模拟异步任务(如接口请求,延迟 500ms 完成)
function asyncTask(index) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`任务 ${index + 1} 完成`);
resolve(`结果 ${index + 1}`); // 任务完成后返回结果
}, 500);
});
}
// 顺序执行 10 个异步任务
async function runTasksInOrder() {
const results = []; // 存储所有任务结果
for (let i = 0; i < 10; i++) {
const result = await asyncTask(i); // 等待当前任务完成
results.push(result); // 收集结果
}
console.log("所有任务顺序执行完毕", results);
}
runTasksInOrder();
特点:
- 代码简洁、可读性强,逻辑和同步代码一致。
- 支持错误捕获(用
try/catch包裹await语句)。 - 可随时收集每个任务的结果,灵活处理。
错误处理示例:
async function runTasksInOrder() {
const results = [];
for (let i = 0; i < 10; i++) {
try {
const result = await asyncTask(i);
results.push(result);
} catch (err) {
console.error(`任务 ${i + 1} 失败:`, err);
// 可选:失败后继续执行下一个任务,或中断(return)
}
}
console.log("所有任务执行完毕", results);
}
二、Promise 链式调用(then() 链式,兼容旧环境)
在 async/await 出现前,Promise 链式调用是顺序执行异步任务的标准方案。核心是:每个 then() 的回调返回下一个异步任务,形成链式依赖。
示例:
// 模拟异步任务(同上)
function asyncTask(index) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`任务 ${index + 1} 完成`);
resolve(`结果 ${index + 1}`);
}, 500);
});
}
// 链式调用顺序执行 10 个任务
function runTasksWithPromiseChain() {
let promiseChain = Promise.resolve(); // 初始 Promise(已完成)
const results = [];
for (let i = 0; i < 10; i++) {
// 每个 then() 等待前一个任务完成,再执行下一个
promiseChain = promiseChain.then(() => {
return asyncTask(i).then((result) => {
results.push(result);
});
});
}
// 所有任务完成后执行
promiseChain.then(() => {
console.log("所有任务顺序执行完毕", results);
});
}
runTasksWithPromiseChain();
特点:
- 无需
async/await,兼容 ES6 环境。 - 逻辑依赖链式结构,任务数量多时,代码嵌套略繁琐(不如
async/await直观)。 - 错误处理:在链式末尾加
catch()捕获所有任务的错误,或在每个then()后加catch()单独处理。
三、递归调用(手动控制执行顺序)
核心逻辑:定义递归函数,执行当前任务后,递归调用自身执行下一个任务,直到所有任务完成。
示例:
// 模拟异步任务(同上)
function asyncTask(index) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`任务 ${index + 1} 完成`);
resolve(`结果 ${index + 1}`);
}, 500);
});
}
// 递归顺序执行
function runTasksWithRecursion() {
const results = [];
const totalTasks = 10;
// 递归函数:执行第 n 个任务
function runTask(n) {
if (n >= totalTasks) {
// 所有任务完成
console.log("所有任务顺序执行完毕", results);
return;
}
// 执行当前任务,完成后递归执行下一个
asyncTask(n)
.then((result) => {
results.push(result);
runTask(n + 1); // 执行下一个任务
})
.catch((err) => {
console.error(`任务 ${n + 1} 失败:`, err);
runTask(n + 1); // 失败后继续执行,或 return 中断
});
}
runTask(0); // 从第 1 个任务开始(索引 0)
}
runTasksWithRecursion();
特点:
- 无需循环,通过递归控制顺序,适合动态生成任务列表的场景。
- 代码逻辑清晰,但需注意递归深度(10 个任务完全无压力,百级以上也无需担心栈溢出,因为是异步递归)。
四、Promise.all() 改造(不推荐,仅作对比)
Promise.all() 本身是并行执行所有任务,但如果强行用它顺序执行(每次只传一个任务),本质是 then() 链式的变体,代码冗余,不推荐使用。
不推荐示例(仅作对比):
function runTasksWithAll() {
const results = [];
let index = 0;
function nextTask() {
if (index >= 10) {
console.log("所有任务顺序执行完毕", results);
return;
}
// 每次只执行一个任务,完成后执行下一个
Promise.all([asyncTask(index)])
.then(([result]) => {
results.push(result);
index++;
nextTask();
});
}
nextTask();
}
特点:
- 完全浪费
Promise.all()并行的优势,代码冗余,不推荐。
核心对比与最佳实践
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
async/await + for | 代码简洁、直观,易调试 | 依赖 ES8 环境(需转译旧浏览器) | 现代项目(React/Vue/Node) |
| Promise 链式 | 兼容 ES6,无需转译 | 任务多时有嵌套感,可读性一般 | 旧环境或无 async/await 场景 |
| 递归调用 | 动态任务列表友好,逻辑清晰 | 需手动写递归函数,略繁琐 | 动态生成任务(如接口返回任务列表) |
Promise.all() 改造 | 无明显优点 | 冗余,浪费并行优势 | 无推荐场景 |
关键注意事项
- 避免用
for...in/forEach配合await:forEach是同步迭代,await不会阻塞循环,会导致所有任务并行执行(而非顺序)。
// 错误示例:forEach 中 await 无效,任务并行执行
async function wrongRun() {
const results = [];
[0,1,2,...,9].forEach(async (i) => {
const result = await asyncTask(i);
results.push(result);
});
console.log(results); // 空数组(循环已结束,任务未完成)
}
-
错误处理:顺序执行时,建议给每个任务单独加错误捕获(
try/catch或then().catch()),避免一个任务失败导致整个流程中断(除非业务需要中断)。 -
性能优化:如果任务之间无依赖(仅需顺序执行,无需前一个结果),且想提升性能,可考虑 “有限并行”(如同时执行 2 个任务,完成后补位),但需用额外逻辑控制(如
p-limit库)。
总结
- 现代项目首选
async/await + for 循环:简洁、直观、易维护。 - 旧环境选 Promise 链式调用:兼容 ES6,无需额外依赖。
- 动态任务列表选递归调用:灵活处理不确定数量的任务。
- 坚决避免
forEach + await和Promise.all()强行顺序执行的写法。