Appearance
6. 说说你对 Promise 的理解
难度等级:🔴 高级 分类:JavaScript 归档日期:2026-10-27
一、为什么需要 Promise
在没有 Promise 之前,JS 的异步任务采用的是回调函数的方式,这就会导致回调嵌套,代码难以维护。
Promise 就是为解决这个问题应运而生的。其实 Promise 也没有消除回调函数,then、catch 中传入的依然是回调,函数作为参数也是 JS 的重要特性之一。它改善的是代码的组织方式,并且能够集中处理错误。
二、三种状态
Promise 的实例表示某个任务(操作)的状态,它只有三种状态:等待 pending、已兑现 fulfilled、已拒绝 rejected。一旦变为 fulfilled 或 rejected(统称 settled),就不能再改变。
三、执行器
new Promise 接收一个执行器函数作为参数,创建 Promise 对象时执行器会立即同步执行。它接收两个参数 resolve、reject,都是 Promise 内部提供的函数。
- 执行器
return的值会被忽略,Promise 的状态和结果由resolve或reject的调用决定; - 执行器同步执行过程中抛出异常,也会使 Promise 被拒绝;但执行器中异步执行抛出的异常不会使 Promise 变为
rejected,状态仍是pending; resolve和reject不会结束执行器代码的执行,return会;- 常见写法
return resolve(123)是有效的,相当于resolve(123); return;(resolve的返回值是undefined)。
javascript
new Promise(resolve => {
return resolve(123); // return 的 undefined 会被忽略
});四、then、catch、finally
执行器函数本身是同步执行的,.then 注册的回调才是异步的。(可以参考 thenable 的例子)
可以用 .then() 获取结果,它接收两个参数:p.then(onFulfilled, onRejected)。
同一个 Promise 可以注册多次处理:
javascript
const p = Promise.resolve(100);
p.then(value => console.log('A', value));
p.then(value => console.log('B', value));1. 返回值规则
| 方法 | 回调正常返回普通值 | 回调抛出异常 | 回调返回 Promise |
|---|---|---|---|
.then() | 新 Promise 以返回值兑现 | 新 Promise 以该异常为原因被拒绝 | 新 Promise 接纳返回的 Promise 的最终状态和结果 |
.catch() | 新 Promise 以返回值兑现,从失败中恢复 | 新 Promise 以该异常为原因被拒绝 | 新 Promise 接纳返回的 Promise 的最终状态和结果 |
.finally() | 忽略返回值,沿用之前的状态和结果 | 新 Promise 以该异常为原因被拒绝 | 等待返回的 Promise:成功则沿用之前的状态和结果;失败则以新的拒绝原因为准 |
.then()、.catch():返回的 Promise 最终怎样,新 Promise 就怎样。
javascript
Promise.resolve(1)
.then(() => {
return Promise.resolve(100); // 这个也会走两步微任务
})
.then(value => {
console.log(value); // 100
});
// 上面的例子想到:p1 会同步返回且 p === p1,p2 会走两步微任务且 p 不等于 p2
const p = Promise.resolve(100);
const p1 = Promise.resolve(p);
const p2 = new Promise(resolve => {
resolve(p);
});
// -------
Promise.reject('原来的错误')
.catch(() => {
return Promise.reject('新的错误');
})
.catch(reason => {
console.log(reason); // '新的错误'
});.finally():返回的 Promise 成功时,不会替换之前的结果;失败时才会覆盖。
javascript
Promise.resolve(1)
.finally(() => {
return Promise.resolve(100);
})
.then(value => {
console.log(value); // 1,不是 100
});
Promise.resolve(1)
.finally(() => {
return Promise.reject('清理失败');
})
.catch(reason => {
console.log(reason); // '清理失败'
});补充两点:
- 不写
return,相当于返回普通值undefined; - 严格来说,除了 Promise,返回 thenable(具有可调用
then方法的对象) 也需要按类似规则处理。
表格中的规则都以对应回调实际执行了为前提。
2. 回调参数
| 方法 | 回调接收的参数 |
|---|---|
.then(onFulfilled, onRejected) | 成功回调接收兑现的值 value;失败回调接收拒绝原因 reason |
.catch(onRejected) | 接收拒绝原因 reason |
.finally(onFinally) | 不接收参数 |
3. 传入非函数会被忽略
传入的参数不是函数时会被直接跳过,值原样传给下一个 then。不传参数的 .then() 相当于 .then(x => x)。
javascript
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log); // 1
Promise.resolve(333)
.then(111, 222)
.then(console.log); // 3334. 回调返回普通值、thenable、Promise 的区别
thenable 就是带有可调用的 then 方法的对象。下面都以 .then() 回调的返回值为例。
(1)返回普通值
javascript
const p = Promise.resolve().then(() => {
return 100;
});
p.then(value => {
console.log(value); // 100
});回调返回 100,.then() 创建的新 Promise 就以 100 兑现。
(2)返回自定义的 thenable 对象
javascript
const myThenable = {
then(resolve, reject) {
resolve(200);
}
};
const p = Promise.resolve().then(() => {
return myThenable;
});
p.then(value => {
console.log(value); // 200
});这里不会直接把 myThenable 当作最终结果,而是异步调用它的 then 方法,根据它调用 resolve 或 reject 的情况,确定新 Promise 的结果。
(3)返回原生 Promise 对象
javascript
const p = Promise.resolve().then(() => {
return Promise.resolve(300);
});
p.then(value => {
console.log(value); // 300
});.then() 创建的新 Promise 会跟随返回的 Promise 的状态和结果:这里返回的 Promise 以 300 兑现,所以 p 最终也以 300 兑现。
注意:返回另一个 Promise,不等于直接把那个 Promise 作为 .then() 的返回值。.then() 返回的仍然是一个新 Promise。
javascript
const p = Promise.resolve(100);
const p2 = Promise.resolve(1)
.then(() => {
return p; // 这个也会走两步微任务
});五、静态方法
Promise.all:全部成功才成功,一个失败就失败;Promise.allSettled:等所有项都有结果,汇总成功和失败(会返回一个包含状态和值的列表);Promise.race:谁先出结果就跟谁,无论成功还是失败;Promise.any:谁先成功就用谁,全部失败才失败。
六、要点补充
注册(登记)回调,不等于立刻把对应任务放入微任务队列。 在一个
pending状态的 Promise 上调用then,只是把回调登记在这个 Promise 上,等它 settled 之后回调才会加入微任务队列。补充一个额外话题 Settimeout(()=>null, 3000),执行代码时也只是登记,真正加入任务队列是在条件触发(此处是3s 之后)的时候。用户事件也是同理。和微任务一样,加入队列执行的是回调函数。 另外:3000ms 是最短等待时间,不保证回调准时执行
回调返回一个 Promise 时,会慢两拍,“多两个 tick”可以这样理解。
当
then(() => { return p })或new Promise(resolve => resolve(p))的时候:- 第一步: 发现
p是个对象,就读取它的then属性(仅读取不调用,会触发 getter),发现then是个方法,那 JS 引擎就要小心了,因为then里面可能报错,可能有异步任务,新返回 promise 对象需要依赖它们的状态,不能里面的没完成或者报错了,外面的就直接 settled 了。接下来会包装一个称为NewPromiseResolveThenableJob的微任务加入微任务队列,这是第一步; - 第二步: 当执行到
NewPromiseResolveThenableJob这个微任务时,此时会调用then方法。如果then是自己实现的且是同步的执行,直接把状态传出去,外层 Promise 兑现;如果then是原生的 Promise 的方法,也会调用执行then,但是按照规范要求,then的回调函数必须异步执行,此时把回调加入微任务队列,这个回调一般称为reactionJob,这时外部 promise 的状态依然pending; - 第三步: 自己写的 thenable 没有第三步了,原生的
then方法回调在微任务里排队排到了,就执行它。
- 第一步: 发现
七、Promise 的内部结构
把 Promise 想成一个很简单的状态机,它只对外提供两种能力:
- 订阅(then):“等你有结果了,通知我。”
- 定局(resolve / reject):“你的结果是这个。”
所以内部只有几样东西:
[[PromiseState]]:pending / fulfilled / rejected[[PromiseResult]]:结果值 value[[PromiseFulfillReactions]]:fulfilled 订阅者列表(谁等着我的通知呢)[[PromiseRejectReactions]]:rejected 订阅者列表
重点是:外界(包括另一个 promise)没有任何办法能读取这些内部槽,想知道一个 promise 的结果,唯一的途径就是调用它的 then 订阅它。
八、需要注意的点和容易踩的坑
return Promise 的情况
javascriptconst p = Promise.resolve(123); const p1 = Promise.resolve(p); p === p1; // true const p2 = new Promise(resolve => { resolve(p) }); // 慢两拍 p === p2; // false const p3 = new Promise(resolve => { resolve(Promise.resolve(123)) }); // 这种情况一样的,Promise.resolve(123) 同步得出,但 return 的时候慢两拍thenable 的情况:发现是对象且有
then方法的时候会触发then的 getter,然后才产生NewPromiseResolveThenableJob加入微任务队列。javascriptconst obj = { get then() { console.log('A'); return resolve => { console.log('B'); resolve(100); console.log('C'); }; } };await 的快速路径:这是比较新的规范层面的优化,以前
await确实更慢,要三步,现在快速路径直接拿到p,只是await续体走一个微任务。javascriptconst p = Promise.resolve(100); async function a() { await p; // 快速路径:直接复用 p,await 的续体还是要加入微任务队列的 console.log('A'); } async function b() { await new Promise(resolve => { resolve(p); // 新 Promise 需要接纳 p 的状态 }); console.log('B'); } b(); a(); queueMicrotask(() => console.log('C')); console.log('D');javascriptconst p1 = async function a() { await p; // 快速路径,虽然后面没代码了,但还是要走 await 续体的,就是说还是要走一个微任务,p1 才能兑现结果 };要知道登记的概念
javascriptPromise.resolve() .then(() => console.log('A')) .then(() => console.log('B')) .then(() => console.log('C'));await先求值右侧,再暂停javascriptawait new Promise(resolve => { console.log('A'); resolve(100); console.log('B'); });顺序是:
- 同步执行 Promise 执行器。
- 输出 A,resolve,再输出 B。
new Promise(...)返回。await登记恢复逻辑;此时 Promise 已成功,安排续体微任务。
如果
await等待时 Promise 还是pending,就先登记,等它敲定后才安排续体。通常
resolve(p)、return p慢两拍;resolve(thenable')、return p(thenable')慢一拍,但下面这种情况 thenable 更慢javascriptconst thenable = { then(resolve) { ... } }; const originP = Promise.resolve(p); // p 是一个已兑现的 Promise 对象originP同步直接走完;thenable 要走一个NewPromiseResolveThenableJob的过程,就多了这一拍,后面的reactionJob,thenable 不走。