前端进阶之旅前端进阶之旅
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
首页程序员面试题库Promise 状态不可变
JSJavaScript异步与 Promise

Promise 的状态只能改变一次,多次调用 resolve 或 reject 会怎样?

Promise 的状态从 pending 变为 fulfilled 或 rejected 后就不能再改变。多次调用 resolve 或 reject 时,只有第一次有效,后续调用会被静默忽略。

前端进阶之旅 · 一题精讲更新于 2026.09.05
JavaScript#异步与 Promise#异步编程
先看核心答案读代码示例
理解线索

状态锁定与处理

  1. 状态机pending → fulfilled/rejected,单向且不可逆
  2. 首次生效最先调用 resolve/reject 决定终态
  3. 后续忽略再次调用无效果,无异常抛出不

若 resolve 传入的是 promise,则状态跟随该 promise,但原始 promise 已锁定不可再变

核心回答

先记住这个答案

Promise 的设计保证状态只能从 pending 转换到 fulfilled 或 rejected 一次。一旦状态确定,再次调用 resolve 或 reject 不会产生任何效果。例如,先 resolve 后 reject,Promise 会保持 fulfilled,reject 被忽略;反之亦然。这是为了避免竞态条件和状态不一致,确保 then 回调只触发一次。

  • 首次状态转换生效,后续调用忽略
  • then 回调仅执行一次,无竞态
  • resolve 传入另一个 Promise 会递归等待

状态转换的严格性与内部实现

Promise 内部维护一个状态变量,初始为 pending。当调用 resolve 或 reject 时,引擎会先检查当前状态,只有 pending 才允许转变。一旦转变为 fulfilled 或 rejected,状态便永久固定。这一设计基于 ECMAScript 规范中的 PromiseResolveThenableJob 和 PromiseReject 操作,确保状态转换的原子性。

多次调用 resolve 或 reject 不会报错,也不会覆盖已有状态。例如先 resolve 后 reject,Promise 保持 fulfilled,拒绝原因被丢弃;若先 reject 再 resolve,则保持 rejected。这种忽略体现了“状态不可变”的核心原则,保证后续 then 回调只会收到一致的结果。

多次调用演示JavaScript
const p = new Promise((resolve, reject) => {
  resolve('first');
  reject(new Error('ignored'));
  resolve('second');
});

p.then(
  value => console.log('fulfilled:', value),
  reason => console.log('rejected:', reason.message)
);
// 由于 resolve 先被调用,Promise 保持 fulfilled,后续 reject 和 resolve 被忽略
// 输出:fulfilled: first
查看输出与解释
fulfilled: first

executor 同步执行,第一个 resolve 将状态设为 fulfilled,后续调用均无效,then 只注册一次。

缓存异步操作结果:防止重复初始化

在单页应用中常需缓存一个异步请求的结果,避免多次点击触发重复网络请求。假设用一个 promise 缓存用户数据,请求完成后无论后续调用多少次初始化函数,都应只发出一次请求并复用缓存。

实现中,初始化函数内创建 promise,并在 resolve 后挂到全局变量。由于状态不可变,后续调用同一个 promise 时,then 回调始终获得首次结果,不会重置。若没有该机制,多次调用可能导致多次请求或状态混乱。

缓存示例JavaScript
let cachedPromise = null;

function getUserData() {
  if (!cachedPromise) {
    cachedPromise = new Promise((resolve) => {
      // 模拟请求
      setTimeout(() => resolve({ id: 1, name: 'Alice' }), 100);
    });
  }
  return cachedPromise;
}

// 多次调用返回同一 promise,仅首次请求
const p1 = getUserData();
const p2 = getUserData();
console.log(p1 === p2); // true
p1.then(data => console.log('p1', data.name));
p2.then(data => console.log('p2', data.name));
// 输出:true 后两个日志顺序可能不同,但数据一致
查看输出与解释
true
p1 Alice
p2 Alice

缓存 promise 引用,状态锁定保证二次调用不再发起新请求,所有 then 获得相同结果。

状态锁定的边界与特殊情况

如果 resolve 的参数是另一个 promise(thenable),原始 promise 会立即被锁定(resolved)到该内部 promise,但其状态(settled state)会保持 pending 直到内部 promise 落定。此后,外层 promise 的状态由内部 promise 决定,且任何后续调用 resolve/reject 都无效。例如 resolve(promiseA) 后,若 promiseA 尚未完成,外层 promise 仍处于 pending,但后续调用 resolve/reject 会被忽略。

另一种边界是 executor 中抛出异常。同步抛出会触发 reject,等价于调用 reject(error),并同样遵循一次状态转换原则。若后面再调用 resolve 则无效。了解这些边界有助于避免在复杂异步流中误以为可以重置 promise 状态,实际需重新创建新 promise。

回答前,多想一步

容易答错的地方

认为多次调用会累积或覆盖
错误认为多次 resolve/reject 可以改变最终结果。实际只有第一次有效,后续调用被完全忽略。例如先 resolve(1) 再 resolve(2),最终值仍是 1,不会出现覆盖。
以为会抛出异常或错误
有人担心多次调用会导致异常或未处理错误。实际上,后续调用被静默忽略,不产生任何错误提示。但也因此可能掩盖逻辑错误,比如在条件分支中误调 resolve 后未能发现。
试着用自己的话回答

面试官还会怎么问?

resolve 传入一个 Promise 后,再调用 reject 会怎样?

外层 promise 先锁定为 resolved to the inner promise,即内部 promise 落定后,外层状态将跟随内部,后续 reject 被忽略。例如 resolve(inner) 后调用 reject,最终取决于 inner 的完成状态,reject 无效。

如何判断一个 Promise 是否已经处于锁定状态?

JavaScript 没有公开 API 可以同步查询 promise 状态。可以通过注册 then 回调来观察,但回调在状态落定后才会异步执行,因此不能立即获知状态。也没有可靠的间接探测方法——例如,使用 Promise.race 并不适用,因为它无法区分 promise 是否已落定。

多次调用 resolve 但参数是 thenable,处理有何不同?

如果第一个 resolve 参数是 thenable,promise 会立即被锁定到该 thenable,并异步等待其落定;在此期间,后续调用 resolve/reject 都会被忽略,不会影响内部 promise 的落定。一旦内部 promise 落定,外层 promise 的最终状态随之确定,后续调用依然无效。

从一道题,走向一组知识

把知识连起来

异步与 Promise

Promise 构造器中的 executor 是同步执行还是异步执行?

同属「异步与 Promise」专题,接着看 Promise executor 同步执行 在具体场景中的处理方式。

异步与 Promise

Promise.all 传入空数组时,会怎样?

同属「异步与 Promise」专题,接着看 Promise.all 空数组 在具体场景中的处理方式。

放回知识体系中复习JavaScript 进阶知识

参考资料

  • Promise

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 状态转换的严格性与内部实现
  3. 缓存异步操作结果:防止重复初始化
  4. 状态锁定的边界与特殊情况
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑