Promise的状态吸收
状态吸收指的是两个 promise
,其中一个去吸收另一个的状态,当一个 promise
状态改变时,另一个 promise
也会跟着改变。
可能发生的场景
状态吸收可能发生在多个场景:
// 一个完成的 promise
const promise1 = Promise.resolve('success');
// 一个拒绝的 promise
const promiseReject = Promise.reject('error');
2
3
4
1. 构造函数中的状态吸收
// 新new一个 promise2 ,resolve的是一个 promise1,这时候 promise2 会吸收 promise1 的状态
const promise2 = new Promise((resolve, reject) => {
resolve(promise1);
});
2
3
4
2. then 链式调用中的状态吸收
// then 方法返回的 promise3 会吸收 then 方法中的 promise1 状态
const promise3 = promise1.then((value) => promise1);
2
3. async 函数中的状态吸收
// async 函数返回的 promise4 会吸收 async 函数中的 promise1 状态
async function test() {
return promise1;
}
const promise4 = test();
2
3
4
5
4. Promise.race 和 Promise.all 中的状态吸收
// promise5 会吸收最先完成的 promise 的状态
const promise5 = Promise.race([promise1, new Promise(resolve => setTimeout(resolve, 100))]);
// promise6 会吸收所有 promise 的状态,当所有都完成时才会 fulfilled
const promise6 = Promise.all([promise1, Promise.resolve('another')]);
2
3
4
5
5. Promise.resolve 和 Promise.reject 中的状态吸收
// 直接吸收 promise1 的状态
const promise7 = Promise.resolve(promise1);
// 吸收 promiseReject 的拒绝状态
const promise8 = Promise.reject(promiseReject);
2
3
4
5
6. catch 方法中的状态吸收
// 返回一个新的 promise,promise9 会吸收这个 promise 的状态
const promise9 = promiseReject.catch((reason) => {
return promise1;
});
2
3
4
7. finally 方法中的状态吸收
// 返回一个新的 promise,promise10 会吸收这个 promise 的状态
const promise10 = promise1.finally(() => {
return promiseReject;
});
2
3
4
8. Promise 链中的状态吸收
// promise11 会吸收链中最后一个返回的 promise 的状态
const promise11 = promise1
.then(value => {
console.log(value); // 'success'
return Promise.resolve('chain1'); // 返回一个新的 promise
})
.then(value => {
console.log(value); // 'chain1'
return Promise.reject('chainError'); // 返回一个拒绝的 promise
})
.catch(reason => {
console.log(reason); // 'chainError'
return Promise.resolve('recovered'); // 返回一个新的 promise
});
2
3
4
5
6
7
8
9
10
11
12
13
14
9. Promise.any 和 Promise.allSettled 中的状态吸收
// promise12 会吸收最先 fulfilled 的 promise 的状态
const promise12 = Promise.any([promiseReject, Promise.reject('another'), promise1]);
// promise13 会吸收所有 promise 的状态,无论 fulfilled 还是 rejected
const promise13 = Promise.allSettled([promise1, promiseReject]);
2
3
4
5
10. 嵌套 Promise 中的状态吸收
// promise14 会吸收内部 promise1 的状态
function nestedPromise() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(promise1); // 嵌套返回另一个 promise
}, 100);
});
}
const promise14 = nestedPromise();
2
3
4
5
6
7
8
9
状态吸收的过程
V8引擎
的状态吸收机制
是一个精心设计的过程,涉及微任务队列
的精确调度。当需要吸收一个Promise
的状态时,V8引擎
会创建特定的微任务
并按顺序放入微队列
中执行,主要分为以下两个关键步骤:
1. 准备阶段
在准备阶段
,V8引擎
会进行以下操作:
识别
Promise
依赖关系:引擎会识别出哪些Promise
对象之间存在状态依赖关系
,即一个Promise
的状态需要跟随另一个Promise
的状态变化。这是通过检查resolve
参数是否为thenable
对象(具有then
方法的对象)来实现的。创建
PromiseResolveThenableJob
微任务:当检测到需要吸收状态时,V8引擎
会创建第一个微任务
——PromiseResolveThenableJob
。这个任务负责处理thenable
对象的状态吸收逻辑
,确保状态能够正确地从源Promise
传递到目标Promise
。建立
状态观察者关系
:V8引擎
会在内部建立观察者模式
,需要吸收状态的Promise
(例如promise2
)注册为被观察Promise
(例如promise1
)的状态观察者
。这是通过PromiseReaction
机制实现的,每个Promise
都有一个内部属性[[PromiseReactions]]
,用于存储所有依赖它的Promise
的反应
。保存
执行上下文
:引擎会保存当前执行上下文
,包括需要执行的回调函数
和相关的Promise
引用,确保在微任务
执行时能够正确恢复执行环境。
// 示例代码展示准备阶段
const promise1 = Promise.resolve('success');
const promise2 = new Promise((resolve, reject) => {
// 当resolve(promise1)被调用时,V8引擎进入准备阶段
// 1. 识别promise2依赖于promise1
// 2. 将promise2设置为promise1的状态观察者
// 3. 由于promise1已经是fulfilled状态,保存resolve回调
resolve(promise1);
});
2
3
4
5
6
7
8
9
2. 吸收阶段
在吸收阶段
,V8引擎
会按特定顺序执行微任务队列
中的任务:
执行
PromiseResolveThenableJob
:第一个微任务
PromiseResolveThenableJob
会被执行,它会调用源Promise
的then
方法,并传入两个回调函数
:一个用于处理fulfilled
状态,另一个用于处理rejected
状态。这些回调函数
负责将源Promise
的状态和值传递给目标Promise
。创建
PromiseReactionJob
:当源Promise
的状态改变时,V8引擎
会创建第二个微任务
PromiseReactionJob
。这个任务负责遍历源Promise
的[[PromiseReactions]]
列表,执行所有注册的反应
,包括状态吸收逻辑
。状态传递与锁定:在
PromiseReactionJob
执行过程中,源Promise
的状态和值会被传递给目标Promise
。一旦传递完成,目标Promise
的状态将被锁定
为与源Promise
相同的状态,并且这个状态不可逆转。即使源Promise
后续状态再次改变,也不会影响已经完成状态吸收
的目标Promise
。微任务队列清空:
V8引擎
会持续执行微任务队列
中的所有任务,直到队列为空。这确保了所有相关的状态吸收
操作都能在当前事件循环
的微任务阶段
完成,保证了Promise
状态的一致性。
// 示例代码展示吸收阶段
const promise1 = Promise.resolve('success');
const promise2 = new Promise((resolve, reject) => {
resolve(promise1); // 准备阶段
});
// 吸收阶段:
// 1. promise1的fulfilled状态传递给promise2
// 2. 执行promise2的resolve回调,传入promise1的值'success'
// 3. promise2的状态被锁定为fulfilled,值为'success'
promise2.then(value => {
console.log(value); // 输出: 'success'
});
2
3
4
5
6
7
8
9
10
11
12
13
14
状态吸收的内部机制
在V8引擎
内部,状态吸收
是通过以下精密的机制实现的:
PromiseThenableJob
:当resolve
一个Promise
对象时,V8
会创建一个PromiseThenableJob
微任务,这个任务负责处理状态吸收
的核心逻辑。它会调用源Promise
的then
方法,并传入两个回调函数
:一个用于处理fulfilled
状态,另一个用于处理rejected
状态。这些回调函数
会创建PromiseReaction
对象,并将其添加到源Promise
的[[PromiseReactions]]
列表中。PromiseReaction
:每个Promise
都有一个内部属性[[PromiseReactions]]
,这是一个列表,存储了所有依赖它的Promise
的反应
(reactions)。每个PromiseReaction
对象包含了目标Promise
的引用、处理fulfilled
状态的回调函数
和处理rejected
状态的回调函数
。当源Promise
状态改变时,V8引擎
会遍历这个列表,为每个PromiseReaction
创建一个PromiseReactionJob
微任务。PromiseResolveThenableJob
:这是一个特殊的微任务
,专门用于处理thenable
对象(包括Promise
)的状态吸收
。它的工作流程是:- 调用
thenable
对象的then
方法 - 传入两个
回调函数
,分别处理fulfilled
和rejected
状态 - 这些
回调函数
会确保目标Promise
的状态与thenable
对象的状态保持同步 - 如果
thenable
对象已经是fulfilled
或rejected
状态,会立即创建PromiseReactionJob
微任务
- 调用
这三个机制协同工作,确保Promise
状态能够正确地在不同的Promise
对象之间传递和同步,同时保证了异步操作
的顺序性和一致性。
// 内部实现模拟
function PromiseResolveThenableJob(promiseToResolve, thenable, then) {
// 创建PromiseReaction对象,包含目标Promise的引用和回调函数
const reaction = {
promise: promiseToResolve,
fulfillCallback: value => {
// 当thenable fulfilled时,promiseToResolve也fulfilled
PromiseResolve(promiseToResolve, value);
},
rejectCallback: reason => {
// 当thenable rejected时,promiseToResolve也rejected
PromiseReject(promiseToResolve, reason);
}
};
// 将reaction添加到thenable的[[PromiseReactions]]列表中
if (!thenable[[PromiseReactions]]) {
thenable[[PromiseReactions]] = [];
}
thenable[[PromiseReactions]].push(reaction);
// 调用thenable的then方法,传入处理fulfilled和rejected的回调函数
try {
then.call(
thenable,
value => {
// 创建PromiseReactionJob微任务,处理fulfilled状态
enqueuePromiseReactionJob(reaction, 'fulfill', value);
},
reason => {
// 创建PromiseReactionJob微任务,处理rejected状态
enqueuePromiseReactionJob(reaction, 'reject', reason);
}
);
} catch (error) {
// 如果调用then方法时抛出异常,直接reject目标Promise
PromiseReject(promiseToResolve, error);
}
}
// 执行PromiseReactionJob的函数
function PromiseReactionJob(reaction, type, value) {
if (type === 'fulfill') {
// 执行fulfill回调,传递value
reaction.fulfillCallback(value);
} else {
// 执行reject回调,传递reason
reaction.rejectCallback(value);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
实例
const promise1 = Promise.resolve('success');
const promise2 = new Promise((resolve, reject) => {
resolve(promise1);
});
promise2.then(() => {
console.log(1);
})
.then(() => {
console.log(2);
})
.then(() => {
console.log(3);
});
promise1.then(() => {
console.log(4);
})
.then(() => {
console.log(5);
})
.then(() => {
console.log(6);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
执行解析:
步骤 | 执行任务 | 行号 | 执行结果 | 微队列任务 |
---|---|---|---|---|
1 | 创建 promise1 | 1 | Promise {<fulfilled>: 'success'} | - |
2 | 创建 promise2,准备吸收状态,创建任务到微队列:准备 | 2,3,4 | Promise {<pending>} | 准备 |
3 | 执行promise2的第一个then | 6 | pending 状态,等待跳过,继续执行后续同步代码 | 准备 |
4 | 执行promise1的第一个then | 16,17,18 | fulfilled 状态,执行回调函数->创建打印任务log(4) | 准备 log(4) |
5 | 同步代码执行完毕,取微队列第一个任务准备 执行 | - | 创建吸收状态任务吸收 添加到微队列 | log(4) 吸收 |
6 | 取微队列第一个任务log(4) 执行 | 19,20,21 | 打印4 ,回调完成,执行promise1的第二个then ,创建打印任务log(5) | 吸收 log(5) |
7 | 取微队列第一个任务吸收 执行 | - | promise2吸收状态完成,执行promise2的第一个then ,创建打印任务log(1) | log(5) log(1) |
8 | 取微队列第一个任务log(5) 执行 | 22,23,24 | 打印5 ,回调完成,执行promise1的第三个then ,创建打印任务log(6) | log(1) log(6) |
9 | 取微队列第一个任务log(1) 执行 | 6,10,11 | 打印1 ,回调完成,执行promise2的第二个then ,创建打印任务log(2) | log(6) log(2) |
10 | 取微队列第一个任务log(6) 执行 | - | 打印6 | log(2) |
11 | 取微队列第一个任务log(2) 执行 | 12,13,14 | 打印2 ,回调完成,执行promise2的第三个then ,创建打印任务log(3) | log(3) |
12 | 取微队列第一个任务log(3) 执行 | - | 打印3 | - |
13 | 微队列已清空,程序执行完毕 | - | 最终打印顺序:4 5 1 6 2 3 | - |
注意事项
- 循环依赖:如果两个
Promise
互相吸收对方的状态,会导致无限循环,V8引擎
会检测这种情况并抛出错误。
const promise1 = new Promise(resolve => {
// 循环依赖,会抛出错误
resolve(promise2);
});
const promise2 = new Promise(resolve => {
// 循环依赖,会抛出错误
resolve(promise1);
});
2
3
4
5
6
7
8
9
性能考虑:
状态吸收
涉及微任务
的创建和执行,过多的Promise
链式调用可能会影响性能。调试困难:由于
状态吸收
是异步的,且涉及内部机制,在调试时可能会增加复杂度。
通过状态吸收
机制,Promise
能够实现复杂的状态传递和依赖关系管理,这是Promise
链式调用和异步编程的重要基础。