为什么 promise 状态显示待处理,当它已解析为另一个 promise 时

Why promise state shows pending, when it has resolved to another promise

提问人:Sadiq 提问时间:11/16/2023 最后编辑:Sadiq 更新时间:11/16/2023 访问量:88

问:

下面的代码片段我从著名的 YDKJS - Async & Performance 系列中摘录,为了便于理解,做了一些更改。

var p3 = new Promise( function(resolve,reject){
    resolve( "B" );
} );
var p1 = new Promise( function(res,rej){
  resolve( p3 ); // equivalent to p3.then(res)
} );
var p2 = new Promise( function(resolve,reject){
    resolve( "A" );
} );
p1.then( function(v){
    console.log("In p1");
    console.log(p1);
} );
p2.then( function(v){
    console.log("In p2");
    console.log(p1);
} ); 

尽管记录结果的顺序是按照要求进行的,但是承诺 p1 的状态显示挂起 (at ),我无法理解。我试图通过图片(最后)来描述我到目前为止所理解的,所以你可以纠正我错的地方。console.log(p1)

结果

enter image description here

在第 1 阶段:解析为已解析的 promise。我知道仍将保持待处理状态,并且尚未解析为值。但我听说这相当于p1p3p1resolve(p3)

p3.then(res);  // res is a resolve callback of p1

因此,基于这个假设,由于既已解析又具有注册的处理程序(callback of ),因此它将入到微任务队列(MTQ)中。p3resp1

在第 2 阶段:已解决并具有已注册的处理程序(紫色),它将被附加到 MTQ。p2

在第 3 阶段:现在,由于堆栈上没有要执行的内容,因此首先插入 MTQ 中的 cb(黄色)将进入堆栈进行执行。 以下是我的查询: 执行时,它会将 的状态标记为已完成吗?由于 res 是与 关联的回调。res('B')p1p1

由于除了已实现之外已经有一个注册的处理程序(调用时),它不会像第 4 阶段所示附加到 MTQ 中吗?p1p1.then

如果是这样,那么为什么即使在第 5 阶段仍然显示为待处理?p1

在第 6 阶段:如何突然实现?p1

请帮助我了解我在下面的图片描述中哪里错了?

enter image description here

JavaScript ES6-promise 作业 asynccallback

评论

3赞 deceze 11/16/2023
“虽然记录结果的顺序是按照要求的”——呃,不,它们的顺序是相反的......!?这也是谜团的答案。
0赞 Sadiq 11/16/2023
@deceze 是的,相反的顺序是可预测的。但为什么 p1 挂起尚不清楚
0赞 Adam Jenkins 11/16/2023
只是为了帮助摆脱一些复杂性,使其更容易摸索,您可以替换并观察相同的行为resolve(p3)resolve(Promise.resolve('B')
1赞 Adam Jenkins 11/16/2023
虽然 p3 在第 3 阶段被解析,但 p1 的解析(或拒绝)是(如您所指出的)执行 的结果。因此,当 get 得到解决时,的执行将被添加到队列中,以便在没有其他运行时执行p3.thenp3p3.then
0赞 Sadiq 11/16/2023
@AdamJenkins 没错!那么为什么 p1 根据日志显示待处理?

答:

3赞 Bergi 11/16/2023 #1

为什么 promise 状态显示待处理,当它已解析为另一个 promise 时?

这只是术语。该实现不区分“未解决”和“已解决”挂起状态。

执行时,它会将 的状态标记为 Fulfilled 吗?res('B')p1

是的。

注册的回调不会像第 4 阶段所示附加到 MTQ 中吗?为什么即使在第 5 阶段仍然显示为待处理?p1p1

你的推理大多是正确的。是的,它会的。事实上,如果你在执行回调中写了,这正是会发生的事情,阶段 5 会打印 Promise {<fulfilled>: 'B'}p3.then(resolve)p1console.log

我听说这相当于.所以基于这个假设......resolve(p3)p3.then(resolve);

这种假设并不完全有效。它确实在行为上基本等效,但在微任务计时上并不等效。当您将 thenable(承诺)传递给 时,它实际上会在新的微任务中安排对 的调用,而不是从内部同步调用它。resolve.then(resolve, reject)resolve()

    • 答:被创建,被履行p3p3"B"
    • b:创建、访问、发现它是一个方法,并调度一个回调来调用它p1resolve(p3)p3.then
    • c:被创建,实现p2p2"A"
    • d:注册 promise 的回调p1.then(…)
    • e:看到已经实现并安排回调p2.then(…)p2
  1. 步骤 1b 中的任务运行并调用以将其状态采用为 。它看到已经完成并安排回调p3.then(resolve, reject)p1p3
  2. 步骤 1e 中的任务运行:
    • 答:它记录"In p2"
    • b:它记录 ,它仍然处于挂起状态(但已解析为p1p3)
  3. 步骤 2 中的任务运行并调用 。这将实现并计划在其上注册的回调。resolve("B")p1
  4. 步骤 4 中的任务运行:
    • 答:它记录"In p1"
    • b:它记录了在步骤 4 中已实现的日志。p1"B"

你可以阅读规范,了解这些错综复杂的细节,但实际上你不应该依赖它们(而且它们会不时变化——事实上,你错过的细节是建议改变的)。您有两个独立的 promise 链,您不应该对 和 之间的回调顺序做出任何假设。如果它很重要,你会让承诺相互依赖。p1p2

评论

0赞 Sadiq 11/16/2023
“当你传递一个 thenable(一个承诺)来解析时,它实际上会在一个新的微任务中安排对 .then(resolve, reject) 的调用,而不是从 resolve() 中同步调用它” - 你指的是我所描绘的!
0赞 Bergi 11/16/2023
@Sadiq 你已经用图形描绘了(相当好)你在文本中描述的内容。是的,这就是我所指的。你的“阶段 1”错了——不是调度,而是调度。因此,在“第 3 阶段”中,它还没有兑现承诺,它正在执行调度的方法。resolve(p3)() => resolve("B")() => p3.then(resolve, reject)then() => resolve("B")