JavaScript 异步任务控制关注多个操作的启动、结果聚合、并发上限、取消与过期结果处理。Promise 表示结果,并不自动提供线程、限流或底层操作取消。
聚合结果
- Promise.all:全部成功才兑现,任一拒绝就使聚合 Promise 拒绝,结果数组按输入顺序排列。
- Promise.allSettled:等待全部结束,为每项保留 fulfilled 或 rejected 结果。
- Promise.race:采用第一个落定的结果,可能成功也可能失败。
- Promise.any:采用第一个成功结果,全部失败才以 AggregateError 拒绝。
all 与 race 的聚合结果提前结束,不会自动中止其他已启动操作。all 与 allSettled 的空输入结果可成功,race 的空输入会持续待定,any 的空输入会拒绝。
参考:Promise 官方参考。
限制并发
任务应保存为尚未启动的函数。先 map(fetch) 再分组 await,无法限制已经发出的请求。
以下示例接收有限任务数组,保留输入结果顺序;首个任务失败后,阻止尚未启动的任务继续启动,但不取消已运行任务:
async function runLimited(tasks, limit) {
if (!Number.isInteger(limit) || limit < 1) {
throw new RangeError("limit 必须是正整数");
}
const results = new Array(tasks.length);
let cursor = 0;
let stopped = false;
async function worker() {
while (!stopped) {
const index = cursor++;
if (index >= tasks.length) return;
try {
results[index] = await tasks[index]();
} catch (error) {
stopped = true;
throw error;
}
}
}
await Promise.all(
Array.from({ length: Math.min(limit, tasks.length) }, worker)
);
return results;
}
示例假定每项都是返回值或 Promise 的函数。限并发不等于每秒限流;需要单位时间请求配额时还要控制启动节奏。对于部分失败仍需继续的任务,应在单项内记录失败并采用明确的结果结构。
取消与过期结果
AbortController 可为支持 signal 的 fetch 等操作发出取消信号。它不会回滚服务器已经完成的写入,也不会自动取消任意 Promise。
搜索场景还可为每次请求分配递增序号,只允许最新请求更新数据、错误和 loading。取消旧请求节省资源,忽略旧结果保证界面状态正确,两者可配合使用。
重复提交与刷新登录态
经验上,提交按钮的进行中状态用于避免意外重复操作;真正需要防止重复写入时,应由服务端保证相应幂等语义。请求超时不代表服务端一定未执行。
多个请求同时需要刷新登录态时,可复用同一个进行中的刷新 Promise;刷新成功后按明确规则重试,失败则统一退出。必须限制重试次数,并避免刷新请求再次进入同一刷新循环。