B03 Promise 与 async 异步
B03 Promise 与 async 异步
摘要:Promise 用一个对象表示未来完成或失败的结果,三种状态不可逆。
async函数总是返回 Promise,await等待其落定,错误由try/catch统一捕获。串行与并行的选择决定总耗时,Promise.all快速失败而allSettled收集全部结果。本文给出包装、链式、并行三组写法与四类错误处理位置。
关键词:Promise,async,await,并行,错误传播
所属:frontend/js / B-进阶 | 上一篇:B02-this与原型链 | 下一篇:B04-事件循环与任务队列
1 引言
三个接口串行请求耗时相加,改成并行后总耗时等于最慢的一个;一个接口失败时,Promise.all 会让整批结果丢弃,而页面往往只需要展示成功的部分。
本文回答三个问题:异步结果如何表示、串行与并行如何选、错误在哪里被捕获。范围见表 1-1。
| 项 | 本文覆盖 | 本文不覆盖 |
|---|---|---|
| 状态 | pending/fulfilled/rejected |
微任务调度细节(B04) |
| 组合 | all、allSettled、race、any |
并发限流(C04) |
| 错误 | try/catch、catch、超时与取消 |
全局错误上报(B06) |
2 预备知识
| 项 | 要求 | 核对方式 |
|---|---|---|
| 环境 | Node.js 20+(内置 fetch) |
node -e "console.log(typeof fetch)" |
| 前置 | B01 | 理解回调与作用域 |
| 文件 | js-practice/async.js |
编码 UTF-8 |
3 原理与实现
3.1 三种状态与两条链
Promise 只能从 pending 转向 fulfilled 或 rejected,此后不再变化,如图 3-1 所示。
stateDiagram-v2
[*] --> pending
pending --> fulfilled: resolve(value)
pending --> rejected: reject(error)
fulfilled --> then: 交给下一个 then
rejected --> catch: 交给 catch 或 then 的第二个参数
then --> [*]
catch --> [*]
图 3-1 Promise 的状态迁移只能发生一次
图上可见:状态一旦落定即固定,重复
resolve无效;then返回新 Promise,链上任意一环抛错都会跳到最近的catch。
3.2 串行、并行与错误处理
// js-practice/async.js;运行环境:Node.js 20+
const delay = (ms, value) => new Promise((resolve) => setTimeout(() => resolve(value), ms));
// 1 包装回调为 Promise
function readJson(url) {
return fetch(url).then((res) => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
});
}
// 2 串行:后一步依赖前一步
async function loadProfile(userId) {
const user = await delay(10, { id: userId, name: 'Lin' });
const posts = await delay(10, [{ id: 1, authorId: userId }]);
return { user, posts };
}
// 3 并行:彼此独立时同时发起
async function loadDashboard(userId) {
const [user, posts, stats] = await Promise.all([
delay(20, { id: userId }),
delay(30, [{ id: 1 }]),
delay(10, { views: 42 }),
]);
return { user, posts, stats };
}
// 4 容错并行:部分失败不影响整体
async function loadPartial() {
const results = await Promise.allSettled([
delay(5, 'ok'),
Promise.reject(new Error('bad')),
]);
return results.map((r) => (r.status === 'fulfilled' ? r.value : `failed:${r.reason.message}`));
}
// 5 错误处理:只包住可能失败的语句
async function safeLoad() {
try {
const data = await loadProfile(1);
return data.user.name;
} catch (err) {
console.error('load failed', err.message);
return null;
}
}
// 6 超时:与其他 Promise 竞速
function withTimeout(promise, ms) {
const timer = new Promise((_, reject) =>
setTimeout(() => reject(new Error(`timeout ${ms}ms`)), ms),
);
return Promise.race([promise, timer]);
}
const t0 = performance.now();
const [a, b, c] = await Promise.all([loadProfile(1), loadDashboard(1), loadPartial()]);
console.log(a.user.name, b.stats.views, c); // Lin 42 ['ok','failed:bad']
console.log(await safeLoad()); // Lin
await withTimeout(delay(100, 'slow'), 10).catch((e) => console.log(e.message)); // timeout 10ms
console.log('elapsed', Math.round(performance.now() - t0)); // 约 100
组合方法的差异见表 3-1。
| 方法 | 全部成功 | 任一失败 | 适用场景 |
|---|---|---|---|
Promise.all |
返回结果数组 | 立即以该错误拒绝 | 缺一不可的数据 |
Promise.allSettled |
返回状态与值数组 | 不拒绝,逐项记录 | 可部分展示的列表 |
Promise.race |
返回最先落定的 | 若最先为拒绝即拒绝 | 超时控制 |
Promise.any |
返回最先成功的 | 全部失败才拒绝 | 多源镜像取最快 |
常见错误处理位置见表 3-2。
| 位置 | 能捕获 | 注意 |
|---|---|---|
await 外层 try |
该 await 的拒绝 |
只包必要语句,范围勿过大 |
.catch() |
链上任意一环的拒绝 | 放在链尾可兜住中间错误 |
| 事件回调内 | 不适用,异步错误逃逸 | 回调内部自行 try/catch |
顶层 await |
模块加载失败 | 模块脚本才支持 |
4 验证与讨论
- 执行脚本,确认输出
Lin 42 ['ok','failed:bad']与Lin。 - 把
loadDashboard的Promise.all改为逐个await,对比elapsed是否变长。 - 把一个
delay改成Promise.reject,确认Promise.all直接进入catch。 - 把超时时间改为
1000,确认withTimeout返回原值而非报错。
| 检查项 | 预期结果 | 说明 |
|---|---|---|
| 并行耗时 | 约等于最慢任务 | Promise.all 同时发起 |
| 串行耗时 | 各任务耗时之和 | 逐个 await |
| 快速失败 | 任一拒绝即中断 | Promise.all 语义 |
| 容错收集 | 失败项标记为字符串 | allSettled 保留逐项状态 |
讨论与局限。 await 写在循环体内会退化为串行,需要并行时先构造 Promise 数组再 await Promise.all。另,Promise.race 不会取消落败的请求,真正中止需要 AbortController,见 C04。
5 常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
循环里 await 很慢 |
逐条串行 | 收集 Promise 后用 Promise.all |
try/catch 抓不到错误 |
漏了 await |
确认每个异步调用都有 await |
| 一个接口失败整页空白 | 用了 Promise.all |
改用 allSettled 分别处理 |
UnhandledPromiseRejection |
拒绝后没有任何 catch |
链尾加 catch 或外层 try |
| 超时后请求仍在跑 | 只做了竞速 | 配合 AbortController 取消 |
6 结论
| 序号 | 结论 | 依据 |
|---|---|---|
| 1 | async 函数返回 Promise,异常即拒绝 |
图 3-1 |
| 2 | 独立任务并行,用 Promise.all |
表 3-1 |
| 3 | 允许部分失败时用 allSettled |
表 3-1 |
| 4 | 超时用 race,取消用 AbortController |
3.2 节 |
- 上一篇:B02-this与原型链
- 下一篇:B04-事件循环与任务队列
- 返回:B00-总结 | 科目 README
参考文献
[1] MDN Web Docs · 使用 Promise,https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises(访问于 2026-10-31)
[2] ECMA-262 · 27.2 Promise Objects,https://tc39.es/ecma262/#sec-promise-objects(访问于 2026-10-31)

评论 0
还没有评论,快来抢沙发吧~