跳到主要内容

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:队列 + 完成再取下一个。

全栈接口层同样适用:批量写库、批量调模型,都先问「突发并行上限是多少」。

要点​

  1. Promise 只结算一次;catch 默认把链救回 fulfilled
  2. await 漏写 = 得到 Promise 而不是值,错误也接不住
  3. 要全部成功用 all;要失败也收齐用 allSettled
  4. race 超时不等于取消另一边
  5. 未处理拒绝要有全局兜底,但主路径仍应显式 catch

面试速记​

  1. Promise 三种状态? pending / fulfilled / rejected,结算不可逆。
  2. then 和 setTimeout 谁先? then 微任务,setTimeout 宏任务;见事件循环篇。
  3. all 和 allSettled 区别? all 一拒俱拒;allSettled 等全部结束并带 status。
  4. async 函数里 throw 会怎样? 返回的 Promise reject;调用方要 await + try/catch 或 .catch。
  5. 为什么有 unhandledrejection? 拒绝没有被任何 catch / 后续 then 的第二参数接住。