Promise 是现代 JavaScript 的基础,但掌握异步控制流需要超越基本的 .then() 链。本文探讨了中高级开发人员生产应用程序所需的高级 Promise 模式,从并发控制到错误处理和取消。
超越基础的承诺景观
JavaScript 在 Promise 上提供了四种静态方法来组合异步操作,每种方法适合不同的场景:
| 方法 | 解决时间 | 拒绝何时 | 使用案例 |
|---|---|---|---|
| 占位符_0 | 所有承诺兑现 | 任何承诺都被拒绝 | 并行 API 调用,全部必需 |
| 占位符_0 | 所有的承诺都兑现了 | 从来没有 | 结果好坏参半,记录所有结果 |
| 占位符_0 | 第一个承诺落定 | 第一个承诺被拒绝 | 超时竞争条件 |
| 占位符_0 | 第一个承诺解决了 | 所有承诺均被拒绝 | 首次成功响应模式 |
const results = await Promise.allSettled([
fetch("/api/users").then(r => r.json()),
fetch("/api/posts").then(r => r.json()),
fetch("/api/comments").then(r => r.json()),
]);
const successful = results.filter(r => r.status === "fulfilled").map(r => r.value);
当您需要处理部分结果(即使某些操作失败)时,Promise.allSettled 特别有用,例如批量数据同步,其中单个记录失败不应中止整个批次。
并发控制
同时运行太多 Promise 可能会压垮 API、数据库或浏览器。并发限制器对任务进行排队并确保只有指定数量的任务同时执行:
async function pMap(items, fn, concurrency = 5) {
const results = [];
const executing = new Set();
for (const item of items) {
const p = fn(item).then(result => {
executing.delete(p);
return result;
});
executing.add(p);
results.push(p);
if (executing.size >= concurrency) {
await Promise.race(executing);
}
}
return Promise.all(results);
}
// Process 100 URLs with max 5 concurrent requests
const data = await pMap(urls, url => fetch(url).then(r => r.json()), 5);
此模式支持背压:当达到并发限制时,循环将暂停,直到至少一个任务完成。对于更复杂的需求,p-limit、p-queue 和 p-throttle 等库提供了附加功能,包括优先级队列和速率限制。
异步/等待最佳实践
循环中的顺序 await 是一个常见的性能缺陷。每次迭代都会等待前一个 Promise 解析后再开始下一个 Promise,即使没有依赖项也是如此:
// Slow: sequential execution
for (const id of ids) {
const data = await fetch(`/api/items/${id}`).then(r => r.json());
process(data);
}
// Fast: parallel execution
const results = await Promise.all(
ids.map(id => fetch(`/api/items/${id}`).then(r => r.json()))
);
results.forEach(process);
仅当每个步骤依赖于前一个结果时才使用顺序 await 。对于独立操作,始终首选 Promise.all 以最大化吞吐量。异步函数中的错误处理受益于带有类型化错误类的结构化 try-catch:
class ApiError extends Error {
constructor(status, message) {
super(message);
this.status = status;
}
}
async function fetchData(url) {
const response = await fetch(url);
if (!response.ok) {
throw new ApiError(response.status, await response.text());
}
return response.json();
}
错误处理和弹性
生产应用程序必须优雅地处理 Promise 拒绝。全局拒绝跟踪捕获未处理的错误:
// Browser
window.addEventListener("unhandledrejection", (event) => {
console.error("Unhandled rejection:", event.reason);
event.preventDefault();
});
// Node.js
process.on("unhandledRejection", (reason, promise) => {
console.error("Unhandled rejection at:", promise, "reason:", reason);
});
对于不稳定的外部服务,断路器模式可以防止重复调用失败的端点:
class CircuitBreaker {
constructor(fn, options = {}) {
this.fn = fn;
this.failureCount = 0;
this.threshold = options.threshold || 5;
this.timeout = options.timeout || 30000;
this.state = "CLOSED";
}
async call(...args) {
if (this.state === "OPEN") {
throw new Error("Circuit breaker is open");
}
try {
const result = await this.fn(...args);
this.failureCount = 0;
return result;
} catch (error) {
this.failureCount++;
if (this.failureCount >= this.threshold) {
this.state = "OPEN";
setTimeout(() => { this.state = "HALF_OPEN"; }, this.timeout);
}
throw error;
}
}
}
使用 AbortController 取消
Promise 本身并不是可取消的,但 AbortController 提供了一种标准机制来中止获取请求并通过异步流传播取消:
function createTimeoutSignal(ms) {
const controller = new AbortController();
setTimeout(() => controller.abort(), ms);
return controller.signal;
}
async function fetchWithTimeout(url, ms = 5000) {
const signal = createTimeoutSignal(ms);
try {
const response = await fetch(url, { signal });
return response.json();
} catch (error) {
if (error.name === "AbortError") {
throw new Error(`Request timed out after ${ms}ms`);
}
throw error;
}
}
此模式支持任何异步操作超时、请求重复数据删除(其中正在进行的请求在调用方之间共享)以及组件卸载时清理异步流。
结论
高级 Promise 模式将异步 JavaScript 从容易出错的状态转变为健壮且可维护的状态。为每个场景选择正确的 Promise 组合器,限制并发以保护下游服务,构建异步函数以提高可读性,实现断路器以实现弹性,并使用 AbortController 来取消流。这些模式构成了现代 JavaScript 应用程序中生产级异步代码的基础。

