B03 Promise 与 async 异步

2026-10-31 17:170 阅读约 13 分钟JavaScript

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 验证与讨论

  1. 执行脚本,确认输出 Lin 42 ['ok','failed:bad'] 与 Lin。
  2. 把 loadDashboard 的 Promise.all 改为逐个 await,对比 elapsed 是否变长。
  3. 把一个 delay 改成 Promise.reject,确认 Promise.all 直接进入 catch。
  4. 把超时时间改为 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 节

参考文献

[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

0/2000

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