← 返回题目列表

Promise 的状态和链式调用原理是什么?

高频 中等 第 16 / 29 题 更新于 2026/07/27
JavaScriptPromise异步

简化版

Promise 有 pendingfulfilledrejected 三种状态,状态一旦改变就不可逆。then 会返回新的 Promise,因此可以链式调用;回调返回普通值会包装成成功 Promise,抛错或返回 rejected Promise 会进入后续失败链路。

详细版

Promise 状态:

  • pending:等待中。
  • fulfilled:已成功。
  • rejected:已失败。

链式调用示例:

fetchUser()
  .then(user => fetchOrders(user.id))
  .then(orders => render(orders))
  .catch(err => showError(err));

then 的返回值决定下一个 then 收到什么:

  • 返回普通值:下一个 then 接收该值。
  • 返回 Promise:下一个 then 等它完成。
  • 抛出错误:进入最近的 catch

Promise 回调属于微任务,所以会在同步代码之后、下一个宏任务之前执行。

完整版教学

一、Promise 解决了什么问题

Promise 主要解决异步流程组织问题。回调嵌套太深会形成回调地狱,错误处理分散,流程难读。Promise 把异步结果抽象成一个“未来会完成或失败的值”,让结果和值的错误沿同一条链组合。它本身并不会自动取消底层网络或定时器,取消仍需 AbortSignal 等业务协议配合。

二、状态只能改变一次

Promise 从 pending 变成 fulfilledrejected 后,就不能再改变。

const p = new Promise((resolve, reject) => {
  resolve(1);
  reject(new Error('fail'));
});

这里最终只会成功为 1,后面的 reject 无效。这保证了异步结果稳定。

三、then 为什么能链式调用

then 不会返回原来的 Promise,而是返回一个新的 Promise。新 Promise 的状态由回调执行结果决定。

Promise.resolve(1)
  .then(x => x + 1)
  .then(x => console.log(x)); // 2

第一个回调返回 2,这个值成为第二个 then 的输入。

四、错误传播机制

Promise 链中的错误会向后传播,直到遇到 catch 或第二个失败回调。

Promise.resolve()
  .then(() => {
    throw new Error('boom');
  })
  .catch(err => console.log(err.message));

这让异步错误处理更接近同步代码的 try/catch 思路。

五、面试追问与工程落地

Promise 高频追问是“then 里返回 Promise 会怎样”。如果返回的是 Promise,外层新 Promise 会采用这个 Promise 的最终状态;如果返回的是 thenable 对象,也会尝试按 thenable 解析。这就是 Promise 链能扁平化异步流程的原因。

还会追问 catch 后链路是否继续。catch 本质上也是返回新 Promise。如果 catch 里没有继续抛错,而是返回普通值,后面的 then 会走成功分支。这在错误兜底、降级数据、重试逻辑中很常见。

工程中要小心“吞错”。如果 catch 只打印日志但不抛出,调用方会以为流程成功。公共请求库通常要统一错误规范:哪些错误在内部转成业务提示,哪些错误继续 reject 给页面处理。

六、区分 resolved、settled 与 thenable 吸收

“resolved 就等于 fulfilled”是不准确的。一个 Promise 可以通过 resolve(otherPromise) 锁定到另一个仍 pending 的 Promise,此时它已经 resolved,却还没有 settled;只有采用对象最终 fulfilled 或 rejected 后,外层才进入对应终态。规范中的解析过程还会读取普通对象的 then,把 thenable 的结果吸收到新 Promise 中。

let release;
const inner = new Promise(resolve => { release = resolve; });
const outer = new Promise(resolve => resolve(inner));
// outer 已锁定跟随 inner,但仍 pending
release(42);
outer.then(console.log); // 42
回调结果then 返回的新 Promise 如何处理
返回 7fulfilled 为 7
抛出 Errorrejected 为该 Error
返回 Promise采用该 Promise 最终状态
返回带可调用 then 的对象异步执行 thenable 解析 Job
返回结果 Promise 自己,形成自解析rejected 为 TypeError

同一个 Promise 连续发起 3次处理:resolve(1)reject(2)resolve(3),只有第一次解析尝试能决定后续,结果为 fulfilled 的 1。但如果第一次传入 thenable,外层可能暂时保持 pending,这正是“状态只能变一次”与“解析不一定立刻完成”可以同时成立的原因。

then 每次都创建新的 Promise;画链路时不要把“原 Promise 的状态”和“回调返回值决定的新 Promise 状态”混在一起。

七、常见误区与追问

  • 误区:Promise 构造器会自动把 executor 放到微任务执行。 executor 在构造时同步调用,Promise reaction 才通过宿主调度的 Job 异步执行。
  • 误区:Promise 一旦 resolved 就一定已经 fulfilled。 resolved 也可能表示锁定跟随另一个尚未 settled 的 Promise 或 thenable。
  • 误区:then(fn) 会修改并返回原 Promise。 它创建并返回新的 Promise,所以同一源 Promise 可以派生多条互不相同的链。
  • 追问:为什么 thenable 的 then 要特殊处理? Promise 解析协议需要兼容其他 Promise 实现,通过读取并调用可调用的 then 来采用其最终结果。
  • 追问:catch 后为什么可能进入成功分支? catch 返回普通值等于恢复为 fulfilled;若要让错误继续传播,必须重新抛出或返回 rejected Promise。
  • 追问:finally 能否替换链路的值或错误? 正常完成的 finally 通常透传原结果,但如果它抛错或返回 rejected Promise,新错误会覆盖原结局。
  • 追问:未处理 rejection 何时被报告? 宿主在微任务检查点等时机跟踪是否已有 rejection handler,具体报告事件由浏览器或 Node 的宿主规则决定。

八、加强记忆

Promise 可以记成“一个只能兑现一次的承诺”。状态不可逆,then 返回新 Promise,返回值决定下一步,抛错会沿链路找 catch