javascript异步编程怎么做_如何使用Promise处理异步操作【教程】

Promise 的核心是可靠组合异步逻辑而非替代回调;必须显式调用 resolve/reject,链式调用需 return 值,.catch() 仅捕获前序未处理错误,async/await 仍是 Promise 语法糖。

Promise 不是用来“替代回调”的语法糖,而是把异步流程的控制权交还给开发者——它解决的不是“怎么写异步”,而是“怎么可靠地组合、中断、降级和调试异步逻辑”。

Promise 构造函数里必须执行 resolve 或 reject

常见错误是忘记调用 resolve()reject(),导致 Promise 永远处于 pending 状态,后续 .then().catch() 完全不触发:

  • HTTP 请求失败但没调用 reject(err),错误被静默吞掉
  • 条件分支中只在 if 里 resolve(),else 里没处理,逻辑卡住
  • 异步操作(如 setTimeout)未包裹在 resolve() 中,直接 return 值无效

正确写法示例:

new Promise((resolve, reject) => {
  fetch('/api/data')
    .then(res => res.json())
    .then(data => resolve(data))
    .catch(err => reject(new Error('Fetch failed: ' + err.message)));
});

链式调用中 return 是关键,不是自动透传

.then() 回调的返回值决定下一个 .then() 的输入;不显式 return 就等于返回 undefined,后续步骤会收到 undefined 而非你期望的数据:

  • 忘记 return fetch(...),导致下个 .then() 收到 undefined,调用 .json() 报错 Cannot read property 'json' of undefined
  • 同步计算后没 return,后续 .then() 拿不到加工结果
  • 想抛错中断链路,却写了 throw new Error() 但没 return —— 实际上 throw 本身就能被下一级 .catch() 捕获,无需 return

典型修正:

fetch('/api/user')
  .then(res => res.json()) // ← 必须 return,否则下个 then 拿不到 json 结果
  .then(user => {
    console.log(user.name);
    return user.id; // ← 显式返回,供下个 then 使用
  })
  .then(id => fetch(`/api/profile/${id}`));

错误捕获不能只靠最后的 .catch()

.catch() 只能捕获它之前(链路上)未被处理的 rejection。多个并行请求、嵌套 Promise、或中间漏掉 .catch() 都会导致错误丢失:

  • Promise.all([p1, p2]) 时,任一 Promise reject 整体就 reject,但如果你没在 all() 后接 .catch(),错误就变成 unhandled rejection
  • .then(successHandler) 没配 .catch(),而 successHandler 内部抛错,这个错不会被外层 .catch() 捕获(除非你用 .then(success, fail) 形式)
  • 浏览器中未捕获的 Promise rejection 会触发 unhandledrejection 事件,但 Node.js 默认直接崩溃(v15+)

稳妥做法是:每个独立异步操作后加 .catch(),或至少在链尾补上 .catch(console.error),并在开发环境监听全局事件:

window.addEventListener('unhandledrejection', event => {
  console.error('Unhandled promise rejection:', event.reason);
});

async/await 不是 Promise 的替代品,而是语法糖

async function 本质仍是构造 Promise,所有限制和陷阱一个不少:

  • await 后面如果不是 Promise,会被自动包装成 Promise.resolve(value),但别因此忽略错误路径
  • try/catch 能捕获 await 的 rejection,但无法捕获 setTimeout 里的异步错误(除非那个 timeout 被包进 Promise)
  • 多个 await 串行执行,性能可能比 Promise.all() 差很多;该并发的地方别傻等

例如并发获取用户和配置:

// ❌ 串行,慢
const user = await fetchUser();
const config = await fetchConfig();

// ✅ 并发,快
const [user, config] = await Promise.all([
  fetchUser(),
  fetchConfig()
]);

真正难的从来不是“怎么写 async”,而是判断哪个环节该 reject、哪个该降级、哪个该重试、哪个该记录日志——这些决策藏在业务逻辑里,不在语法里。