先记住这个答案
Promise 的状态只能从 pending 变成 fulfilled 或 rejected,而且只能变一次。第一次调用 resolve 或 reject 决定结果,之后不管再调用多少次、调用哪个,都会被静默忽略,不会抛错,也不会改变已有的值。比如先 resolve('ok') 再 reject(err),Promise 仍然是 fulfilled,值是 'ok'。要注意的是 resolve 只是改状态,不会让 executor 停下来,后面的代码会照常执行。
- 状态只能从 pending 变一次,变了就定死
- 后续的
resolve、reject和抛错都被静默忽略 resolve不会中断代码,想停要写returnresolve一个 Promise 后就锁定了,再reject也无效
在 executor 里连续调用会发生什么
最直接的办法是把几种调用写在一起跑一遍。
resolve,再 reject,再 resolveJavaScriptconst p = new Promise((resolve, reject) => {
resolve('第一次')
reject(new Error('被忽略'))
resolve('第二次') // 同样被忽略
console.log('executor 继续执行')
})
p.then(
(v) => console.log('fulfilled', v),
(e) => console.log('rejected', e.message)
)查看输出与解释
executor 继续执行
fulfilled 第一次两个现象要分开记。第一,只有「第一次」生效,后面的 reject 和 resolve 没有任何效果。第二,console.log 照样打印了,说明 resolve 不是 return。如果 resolve 之后还有请求、写缓存之类的逻辑,它们都会执行。习惯写法是 return resolve(value),一次把状态和流程都收住。
resolve 之后再 throw 也一样会被忽略。executor 抛错本质上等于调用 reject,状态已经定了,这个错误就被吞掉了,控制台里也看不到。
resolve 一个 Promise 时的「锁定」状态
resolve 的参数如果是另一个 Promise,外层不会立刻兑现,而是跟着那个 Promise 走。这时外层的状态还是 pending,但已经没法再改了。
rejectJavaScriptconst slow = new Promise((r) => setTimeout(() => r('slow'), 100))
const p = new Promise((resolve, reject) => {
resolve(slow) // p 锁定跟随 slow,此时仍是 pending
reject(new Error('来晚了')) // 被忽略
})
p.then(
(v) => console.log('fulfilled', v),
(e) => console.log('rejected', e.message)
)查看输出与解释
fulfilled slow规范里把这种情况叫 resolved:已经决定了跟谁,但还没有最终结果。所以「pending 状态下还能改」这句话不准确,准确说法是「还没调用过 resolve 或 reject 时才能改」。
- 为什么这样设计
- 一个 Promise 可能被很多地方
then,也可能在兑现很久之后才有人then。如果状态还能变,先拿到结果的和后拿到结果的就会不一致。状态定死之后,Promise 就像一张签好的收据,谁来看、什么时候来看,内容都一样,每个then回调也最多只会被调用一次。
容易答错的地方
- 说多次调用会报错
- 不会报错,就是静默忽略。这也是它难排查的地方:代码里写了
reject,调试时发现根本没进catch,往往就是前面已经resolve过了。 - 说
resolve之后代码就不执行了 resolve只是一个普通的函数调用,改完状态就返回了。后面的代码是否执行只取决于你有没有return。- 以为可以把 Promise 重置回 pending
- Promise 没有提供任何重置或取消的方法。需要「重来一次」就新建一个 Promise,需要取消就用
AbortController之类的机制在外面控制。
面试官还会怎么问?
这个特性在实际开发里有什么用?
写超时控制时很常用。把请求和定时器放进同一个 Promise:请求回来就 resolve,定时器到点就 reject,谁先调用谁生效,另一个自动作废,不用额外写标志位去判断「是不是已经超时了」。
手写 Promise 时怎么保证状态只改一次?
在 resolve 和 reject 函数开头判断状态,只有 this.state === 'pending' 时才修改状态、保存值并执行回调,否则直接 return。要支持上面的锁定行为,还得再加一个「是否已 resolve 过」的标志,因为跟随另一个 Promise 期间状态仍是 pending。
状态定了之后再调用 then 还能拿到结果吗?
能。结果被保存在 Promise 内部,任何时候调用 then 都能拿到同一个值,只是回调仍然会放进微任务队列异步执行,而不是立刻同步执行。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。