Promise 与异步错误
Promise 把「稍后完成的值」做成对象:待定 → 成功或失败,且只结算一次。async/await 是它的语法糖,调度仍走 事件循环 的微任务。限流手写见 async-pool。
解决什么问题
| 痛点 | Promise 怎么帮 |
|---|---|
| 回调地狱 | then 链式;async/await 写成同步外观 |
| 不知道何时结束 | 状态机:pending / fulfilled / rejected |
| 错误被吞 | catch / try/catch(await)/ unhandledrejection |
| 多请求一起 | all / allSettled / race / any |
一句话:Promise 是一次性结算的异步值;错误必须有人接,否则会在宿主里变成未处理拒绝。
状态与结算
const p = new Promise((resolve, reject) => {
resolve(1);
reject(new Error("ignored")); // 无效:已经 fulfilled
});
| 规则 | 含义 |
|---|---|
| 只变一次 | 先 resolve 再 reject,后者被忽略 |
then 返回新 Promise | 可链式;回调里抛错会 reject 这条链 |
resolve(thenable) | 会吸收另一个 Promise/thenable 的结果 |
Promise.resolve(1)
.then((x) => x + 1)
.then((x) => {
throw new Error("boom");
})
.catch((err) => {
console.log(err.message); // boom
return 0; // 恢复为 fulfilled
})
.then((x) => console.log(x)); // 0
catch 之后链默认恢复成功。要继续失败就再 throw 或 return Promise.reject(...)。
async/await
async function load() {
try {
const user = await fetchUser();
const orders = await fetchOrders(user.id); // 串行
return orders;
} catch (err) {
console.error(err);
throw err;
}
}
| 写法 | 实际 |
|---|---|
async function | 一定返回 Promise |
await expr | 对 expr 做 Promise.resolve,后续代码进微任务 |
try/catch | 只接 await 到的拒绝;漏写 await 则接不住 |
并行不要无脑串 await:
const [a, b] = await Promise.all([fetchA(), fetchB()]);
组合子
| API | 成功条件 | 失败条件 | 典型用途 |
|---|---|---|---|
Promise.all | 全部 fulfilled | 一个 reject 就整组失败 | 必须都成功 |
Promise.allSettled | 等全部结束 | 不短拒 | 要看每个谁成谁败 |
Promise.race | 第一个结算(成或败) | 第一个若是败则败 | 超时:race([job, timeout]) |
Promise.any | 第一个 fulfilled | 全部 reject 才失败(AggregateError) | 多源择优 |
超时示例:
function withTimeout(p, ms) {
const t = new Promise((_, reject) => {
setTimeout(() => reject(new Error("timeout")), ms);
});
return Promise.race([p, t]);
}
注意:race 输了不会取消另一个任务;只是不再等它的结果。真正取消要 AbortController(fetch)或业务层合作式取消。
错误接到哪
new Promise 执行器里 throw → 该 Promise reject
then 回调 throw / return reject → 下游 reject
async 函数 throw / await 拒绝 → 返回的 Promise reject
谁都不 catch → 未处理拒绝
浏览器:window.addEventListener("unhandledrejection", ...)
Node:process.on("unhandledrejection", ...)(未处理可能让进程以非零退出,视版本与配置)
空 catch 比不写更危险:错误被吞,现场消失。至少打日志或上报。
// 启动即挂上:漏网之鱼
queueMicrotask(() => {
Promise.reject(new Error("lost")); // 没有 catch → unhandledrejection
});
并发限流
Promise.all 会一次性发起全部请求。连接数、上游 QPS 扛不住时要池化:同一时刻最多 N 个 in-flight。思路见 async-pool:队列 + 完成再取下一个。
全栈接口层同样适用:批量写库、批量调模型,都先问「突发并行上限是多少」。
要点
- Promise 只结算一次;
catch默认把链救回 fulfilled await漏写 = 得到 Promise 而不是值,错误也接不住- 要全部成功用
all;要失败也收齐用allSettled race超时不等于取消另一边- 未处理拒绝要有全局兜底,但主路径仍应显式 catch
面试速记
- Promise 三种状态? pending / fulfilled / rejected,结算不可逆。
then和setTimeout谁先?then微任务,setTimeout宏任务;见事件循环篇。all和allSettled区别?all一拒俱拒;allSettled等全部结束并带 status。async函数里 throw 会怎样? 返回的 Promise reject;调用方要 await + try/catch 或.catch。- 为什么有 unhandledrejection? 拒绝没有被任何
catch/ 后续then的第二参数接住。