人人都会AI编程

12.2 Promise 核心原理与用法

更新时间:2026-07-11

在上一节中我们提到,回调函数虽然能解决异步问题,但多层嵌套会导致“回调地狱”——代码向右无限缩进、难以维护、错误处理分散。Promise 的出现正是为了将异步代码从嵌套的回调中解救出来,以一种更线性、更可控的方式组织异步操作。

12.2.1 Promise 是什么

Promise 是一个代表了某个异步操作最终结果的对象。你可以把它想象成一个“承诺”:它当前可能还没有结果,但在未来某个时刻会给出一个确定的答复——要么成功,要么失败。

// 创建一个 Promise,模拟一个异步操作
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const success = Math.random() > 0.5;
    if (success) {
      resolve('数据获取成功');
    } else {
      reject('请求失败');
    }
  }, 1000);
});

Promise 拥有三种互斥的状态:

  • pending(进行中):初始状态,操作尚未完成。
  • fulfilled(已成功):操作成功完成,通过 resolve 传出结果值。
  • rejected(已失败):操作失败,通过 reject 传出失败原因。

最核心的规则:状态一旦从 pending 变为 fulfilled 或 rejected,就不可再变。这种不可逆性保证了异步操作的终点是确定、纯粹的,也杜绝了回调中可能出现的“多次调用 resolve / reject”问题。

12.2.2 then、catch、finally

Promise 通过 thencatchfinally 方法提供了统一的回调注册方式。

then

then 方法接收两个可选的回调参数:第一个处理成功,第二个处理失败。

promise.then(
  (value) => { console.log('成功:', value); },
  (reason) => { console.log('失败:', reason); }
);

更重要的是,then 总是返回一个新的 Promise,这使链式调用成为可能:

doSomething()
  .then(result => processResult(result))
  .then(processed => saveResult(processed))
  .then(() => console.log('全部完成'))
  .catch(error => console.error('途中出错:', error));

链式调用彻底消除了多层回调的嵌套问题,让异步流程读起来像是同步顺序执行。

catch

catch(onRejected)then(null, onRejected) 的语法糖,用来统一捕获链上前端任何环节抛出的错误。Promise 的错误会沿着链一直向后传递,直到遇到第一个 catch

fetchUser()
  .then(parseData)
  .then(saveData)
  .catch(error => {
    // fetchUser、parseData、saveData 中任何一个环节出错都会落到这里
    console.error('操作失败:', error);
  });

finally

finally(callback) 无论 Promise 最终状态是 fulfilled 还是 rejected,都会执行。常用于清理资源、关闭 loading 动画、隐藏加载提示等收尾工作。它不像 thencatch 那样接收数据,也不改变链上传递的结果值。

showLoading();
fetchData()
  .then(data => render(data))
  .catch(error => showError(error))
  .finally(() => hideLoading());

12.2.3 Promise 静态方法

ES6 提供的静态方法极大地丰富了并发场景下的控制能力。

Promise.resolve / Promise.reject

用来快速创建已敲定状态的 Promise,常用于将普通值包装成 Promise,或直接返回一个拒绝的 Promise。

Promise.resolve(42).then(v => console.log(v));      // 42
Promise.reject('fail').catch(e => console.log(e));   // 'fail'

Promise.all

接收一个可迭代对象(通常是数组),等所有 Promise 都成功,才返回包含所有结果数组的 Promise;只要有任何一个失败,立即返回失败原因,其余未完成的结果会被忽略。适合“全部任务都必须成功”的场景,例如同时请求多个接口的数据。

Promise.all([fetchUser(), fetchOrders(), fetchReports()])
  .then(([user, orders, reports]) => {
    // 全部成功,使用数据
  })
  .catch(error => {
    // 任何一个失败都会进到这里
  });

Promise.race

竞态方法,哪个 Promise 先改变状态(无论成功还是失败),就返回那个结果。常用于超时控制。

const timeout = new Promise((_, reject) => {
  setTimeout(() => reject('请求超时'), 5000);
});
Promise.race([fetchData(), timeout])
  .then(data => console.log('成功', data))
  .catch(err => console.log('失败或超时', err));

Promise.allSettled

all 不同,allSettled 会等待所有 Promise 完成,无论成功还是失败,最后返回一个数组,每个元素包含 statusvaluereason。适合需要获知所有操作结果、部分失败也不影响整体判断的场景。

Promise.allSettled([fetchUser(), fetchOrders(), fetchReports()])
  .then(results => {
    results.forEach(r => {
      if (r.status === 'fulfilled') console.log('成功', r.value);
      else console.log('失败', r.reason);
    });
  });

Promise.any

接收一个 Promise 数组,只要其中任何一个成功,就返回那个成功的值;如果所有 Promise 都失败,才拒绝。它相当于 all 的“宽松”兄弟,常用于多源竞速请求同一资源(从多个 CDN 获取同一个文件)。

Promise.any([fetchFromCDN1(), fetchFromCDN2(), fetchFromCDN3()])
  .then(data => useData(data))
  .catch(() => console.error('所有源都失败'));

12.2.4 实用技巧与常见误区

  • 始终要返回结果:在链式调用的回调中,如果要让下一步拿到处理后的数据,必须用 return 返回。忘记 return 会导致后续 then 收到 undefined
  • 避免冗余嵌套:不要在 then 里再去单独创建 Promise 然后再 then,直接用链式调用延伸下去。
  • 错误捕获位置:将 catch 放在链的末端,可以统一处理上游错误;如果需要在中间处理错误并恢复流程,可以在合适位置插入 catch 并提供降级数据。
  • 并发数量控制:当需要同时执行大量异步操作(如批量上传文件)时,应使用队列或限制并发数的方案,而不是一股脑全部放进 Promise.all,以免耗尽网络连接或内存。这部分将在第 12.4 节“异步并发控制”中详细展开。

Promise 改变了 JavaScript 异步编程的面貌,使异步流程从“嵌套回调”演进为“链式表达”。而下一节要学习的 async / await 进一步将这个链式表达变成了几乎是“同步写法”的优雅形态。理解 Promise 的原理,才能真正用好 async / await