返回的 Promise 有什么区别?

4

有两个情况,都返回Promise,并与以下then方法链接。但结果序列是不同的。 一些注释: Promise.resolve(value) -> 立即返回使用值实现的Promise。 并且在then方法中,当我们再次返回值时,它会返回一个带有该值的已完成Promise。 从逻辑上讲不应该有任何区别。两者都是立即... 提前感谢...

Promise.resolve(1)
  .then((v) => {
    console.log(v);
    return Promise.resolve(v + 1);
  })
  .then((v) => {
    console.log(v);
    return Promise.resolve(v + 1);
  })
  .then((v) => {
    console.log(v);
    return v + 1;
  })
  .then((v) => {
    console.log(v);
    return v + 1;
  });

Promise.resolve(10)
  .then((v) => {
    console.log(v);
    return v + 1;
  })
  .then((v) => {
    console.log(v);
    return v + 1;
  })
  .then((v) => {
    console.log(v);
    return v + 1;
  })
  .then((v) => {
    console.log(v);
    return v + 1;
  });

//Result on the console:
//1
//10
//11
//12
//2
//13
//3
//4

//

Promise.resolve(1)
  .then((v) => {
    console.log(v);
    return v + 1;
  })
  .then((v) => {
    console.log(v);
    return v + 1;
  })
  .then((v) => {
    console.log(v);
    return v + 1;
  })
  .then((v) => {
    console.log(v);
    return v + 1;
  });

Promise.resolve(10)
  .then((v) => {
    console.log(v);
    return v + 1;
  })
  .then((v) => {
    console.log(v);
    return v + 1;
  })
  .then((v) => {
    console.log(v);
    return v + 1;
  })
  .then((v) => {
    console.log(v);
    return v + 1;
  });

//Result on the console:
//1
//10
//2
//11
//3
//12
//4
//13

1
异步任务的执行顺序在实际应用中更多或更少是未定义的。(你可以深入了解微任务和事件循环的细节,但为什么要这样做呢?)只要每个 Promise 返回预期的结果,你就不必太关心它何时到达。 - deceze
@deceze 谢谢。 我只是想理解它背后的逻辑。 - sharpenedguy
2个回答

4
重要的区别在于:请记住then会返回一个承诺(我们称之为承诺T)。当你这样做:
return Promise.resolve(v + 1);

当你将 Promise T 解析为一个 Promise(我们称之为 Promise B)时,你会这样做:

return v + 1;

...你正在将 Promise T 解决为立即值。

将 Promise T 解决为 Promise B 会在解决过程中引入额外的“刻度”。Promise T 不再排队调用其解决处理程序,而是必须等待 Promise B 调用它所设置的解决处理程序,然后才能调用它自己的解决处理程序。因此,出现了额外的“刻度”(基本上是微任务队列的另一个循环)。

以下是一个更简单的示例,请注意它在记录“First 2”之前记录“Second 2”:

Promise.resolve(1)
    .then(v => Promise.resolve(v + 1))
    .then(v => console.log(`First: ${v}`));
Promise.resolve(1)
    .then(v => v + 1)
    .then(v => console.log(`Second: ${v}`));

如果没有额外的承诺,它会在“第二个2”之前记录“第一个2”,而如果有额外的承诺,它则记录“第二个2”后再记录“第一个2”:

Promise.resolve(1)
    .then(v => v + 1)
    .then(v => console.log(`First: ${v}`));
Promise.resolve(1)
    .then(v => v + 1)
    .then(v => console.log(`Second: ${v}`));

最近对于像 return await somePromise 这样的 async 函数,已经取消了这个额外的勾选 (而不是仅仅 return somePromise),因为可以通过 async/await 内部处理本地承诺可靠地删除它。但我不确定对于您的情况是否可以可靠地删除它,或者这样做是否会受到必须执行它的人员的重视。它需要将从 then 处理程序返回的 native promise 与任何其他 thenable¹ 区分开来,这可能会有问题。 (但我不能确定它是否会有问题。)
¹ thenable vs. promise : https://promisesaplus.com

虽然从技术上讲这是正确的,但我认为任何涉及方都不希望出现这种情况,这只是一个巧合。人们大多不希望zalgo和代码根据同步/异步具有不同的执行顺序。Promise.resolve 这样工作的事实只是因为我们不想要第二个函数(Promise.cast https://esdiscuss.org/topic/promise-cast-and-promise-resolve),而它最终做了 mapflatMap 两种功能。 - Benjamin Gruenbaum
@BenjaminGruenbaum - 我不是在谈论意图,也不能这样做。:-)(虽然“巧合”似乎是一种错误的描述;所有重要参与者都会意识到从具有规范定义语义的处理程序返回return v + 1return Promise.resolve(v + 1)之间的区别。)我只是在说明OP看到的行为的原因。 - T.J. Crowder
1
那只是因为更容易指定,我记得在某个协作峰会上(柏林2018年?)和Benedikt、Maya和Daniel进行了讨论。当Benedikt向我展示了四个承诺的事情时,我说:“嗯,没有什么是有意为之的——我们应该更改规范,而不是进行优化。”我想这可能是其中一种情况,它只是偶然多等待了一个额外的微观滴答,这是不必要的。 - Benjamin Gruenbaum
1
此外 - 我并不反对 - 只是觉得有趣 :D 这从未被设计成差别,“return foo”应该隐式地成为“return Promise.resolve(foo)”,时间差异(以及严格的微任务调度)是 ECMAScript 规范的一个产物 - Promises/A+ 规范没有做出这样的要求。它只要求执行上下文堆栈仅包含平台代码。 - Benjamin Gruenbaum
1
@sharpenedguy Promise.resolve(Promise.resolve(1))与上面的情况略有不同,因为它具有优化功能:当您将一个promise传递给Promise.resolve时,PromiseResolve抽象操作会检查该promise的构造函数是否与您调用resolve的promise相同。如果是,则只返回您传入的promise,使外部调用成为完全无操作。这意味着const p = Promise.resolve(1); console.log(p === Promise.resolve(p));会输出true - T.J. Crowder
显示剩余6条评论

0

请记住,JavaScript 是单线程的,并且将所有方法添加到队列中。我以下所做的就是承诺应该以某种方式编写的方式。

Javascript 将所有任务添加到第一列中,在队列中执行所有命令,然后在发现异步方法中的列2和3中添加更多任务。

如果您像这样查看它,那么它非常合乎逻辑。

enter image description here


“Javascript不会将所有任务添加到第一列” - then会同步地将处理程序附加到Promise,但是只有在Promise解决时,这些处理程序才会被安排到队列中。 - Bergi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接