【问题标题】:Promise output varies between NodeJs and BrowserNodeJs 和浏览器之间的 Promise 输出不同
【发布时间】:2015-10-23 17:38:40
【问题描述】:

以下代码块在 Node 和浏览器之间的执行方式不同。当然,存在不同的环境、不同的处理时间和竞争条件的可能性。但是根据我对 Promises 的理解,这段代码在环境之间应该是一致的。

我期待 Chrome/浏览器的结果。我不期望 NodeJs 的结果。我不明白为什么每个newPromisethen 链都没有完成之前masterPromisethen 链继续。换句话说,因为一个新的 Promise 在一个 then fn 中返回到 masterPromise 承诺链,我希望新的 Promise 的 then 链在 masterPromise 承诺链恢复之前完成。

如果有人可以在以下实现中戳一个洞,并解释为什么 NodeJs 结果是有效的,我会很高兴!

使用 chrome 44 和节点 12.6。

例如:

'use strict';
var masterPromise = Promise.resolve();
var numbers = [ 1, 2, 3 ];

// function returns a new promise that fulfills in 100ms
// it logs two bits of information--one pre-resolve, & one post-resolve.
// because a `.then` is registered immediately, before the promised is
// fulfilled, i would expect the post-resolve console.log to be logged before
// any other logging
var returnNewPromise = function(number) {
    var resolve;
    var newPromise = new Promise(function(r) { resolve = r; });
    newPromise.then(function() { console.log('registered ' + number + ' (verbatim, syncronous echo)'); });
    setTimeout(function() {
        console.log('registered ' + number);
        resolve();
    }, 100);
    return newPromise;
};

numbers.forEach(function(number) {
    var getChildPromise = function(number) {
        return returnNewPromise(number);
    };
    return masterPromise.then(function() {
        return getChildPromise(number);
    });
});

节点:

registered 1
registered 2
registered 3
registered 1 (verbatim, syncronous echo)
registered 2 (verbatim, syncronous echo)
registered 3 (verbatim, syncronous echo)

铬:

registered 1
registered 1 (verbatim, syncronous echo)
registered 2
registered 2 (verbatim, syncronous echo)
registered 3
registered 3 (verbatim, syncronous echo)

【问题讨论】:

  • 您需要详细说明为什么“根据您对 Promise 的理解”,应该预期一种或另一种行为。我认为这是不确定的,两种模式中的任何一种都是相关规范下的有效行为。
  • @Pointy,够公平的!更新
  • @Pointy,我再次更新以真正获得更窄的焦点。是什么在句法上引起了不确定性,你看到了吗?一位同事研究了这个问题,并认为它也应该是确定性的。然而,他遇到了和我一样的问题,尽管他拼凑了一个很棒的小提琴来证明这个问题! jsfiddle.net/kgwouxg7/1
  • 这不是语法问题,而是语义问题。我认为它绝对没有理由是确定性的。不同的事件组相互之间没有依赖关系,因此调用顺序应该完全没有区别。

标签: javascript node.js google-chrome promise


【解决方案1】:

我不明白为什么每个newPromisethen 链不是 完成之前masterPromisethen 链继续。一个新的 Promise 在 then 中返回到 masterPromise promise-chain fn,所以它不应该在 masterPromise 之前等待吗 承诺链恢复了吗?

没有。看来您的误解是关于“masterPromise 链”:没有这样的东西。
你有一个masterPromise,然后你从它链接了三个不同的then 调用。当masterPromise 解析(立即)时,它会看到 3 个回调并按照注册顺序调用所有这些回调。它不关心这些回调做什么,它们是否是异步的,并且不等待它们的承诺结果。在您的情况下,它们都创建了 Promise,并通过它们推进了它们的子链,但是这 3 个子链彼此完全独立。

也许用更具描述性的日志来充实你的代码有助于理解你在做什么:

function delay(number) {
    console.log("creating promise for", number);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log('resolving promise with', number);
            resolve(number);
        }, 100);
    });
};
function result(n) {
    console.log("received", number);
}

var masterPromise = Promise.resolve();

masterPromise.then(function(){ delay(1).then(result); }); // chain 1
masterPromise.then(function(){ delay(2).then(result); }); // chain 2
masterPromise.then(function(){ delay(3).then(result); }); // chain 3
console.log("created chains");

您将在此处看到的日志是

// .then chain 1    -------,
// .then chain 2    ------- \ -,
// .then chain 3    -------- \ -\ -,
created chains                |  |  |
                              |  |  | 3 then callbacks in the order they were registered
creating promise for 1      <´   |  |
// setTimeout 100   -----,       |  |
                          \     /   |
creating promise for 2     | <-´    |
// setTimeout 100   ------ |-,      /
                           |  \    /
creating promise for 3     |  | <-´
// setTimeout 100   ------ |- |-,
                           |  |  \
…                          |  |  | 3 timeout callbacks in the order they were scheduled
                           |  |  |
resolving promise with 1 <´   |  |
// resolve()                  |  |
[…]                           /  |
resolving promise with 2  <-´    |
// resolve()                     /
[…]                             /
resolving promise with 3   <---´
// resolve()
[…]

在这里我们可以看到 3 个(独立的)resolve() 调用发生。他们将尽快安排各自的回电 (result)。这就是 node 和 Chrome 之间的区别:前者确实在同一滴答声中同时执行超时回调(它们是在同一时间安排的,具有相同的超时时间),而 Chrome 使用单独的滴答声。所以在 node 中,“asap”在三个回调之后,而在 Chrome 中它在它们之间。两种实现都很好。

【讨论】:

    【解决方案2】:

    我不明白为什么每个 newPromise then 链在解析后都没有立即执行

    then-callbacks 不会立即调用。

    var resolve;
    new Promise(function (r) { resolve = r; })
        .then(function () { console.log(2); });
    resolve();
    console.log(1);
    

    日志:

    1
    2
    

    Promise 解析的行为类似于 setTimeout,延迟为零。

    更新:

    由于限制,与setTimeout 的比较并不完全正确: https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout#Minimummaximum_delay_and_timeout_nesting 相似之处在于异步类型的 Promise。

    更新2:

    -----------+------------
    wait       |  resolved
    -----------+------------
    timeout1   |             initial state
    timeout2   |
    -----------+------------
               | timeout1    after 100 ms
    timeout2   |
    -----------+------------
               | resolve1    then Chrome variant
               | timeout2
    -----------+------------
               | timeout2    or Node variant
               | resolve1
    -----------+------------
    

    两种变体均符合规范。似乎 Chrome 队列会在后续超时回调之前解析回调。

    【讨论】:

    • 是的,这就是我认为运行时可以产生任一结果并且是正确的原因。虽然有人可能会想象.then() 会直接调用函数,如果 promise 已经被解析,这不是它应该工作的方式:回调应该使用干净的堆栈运行,所以它是通过某种机制调用的基本上就像这个答案所描述的那样。因此,浏览器可能会在未决超时之前处理它,也可能不会。 (这是我的看法,但这个答案仍然是正确的。)
    • 是的,这对我来说是个新闻。谢谢。很高兴知道这是一个 nextTick 可能的操作。但它仍然没有解释以下内容,这是出乎意料的。 'newPromise' 在 'masterPromise's then's 中返回。不应该在 masterPromise 的 then 链继续之前执行每个 newPromise 的 then 吗?我觉得这份合同没有得到履行。
    • @cdaringe no - 为什么会发生这种情况?
    • 嘿@Pointy,我相信因为这个this rule。正确的?如果 X 有一个 thenable,并且那个 thenable 返回一个 Promise Y,那么 Promise 链会适应 Y 的状态,并且应该解决 Y 的 then 链。假设 Y 的 thenables 返回了一个 Promise Z。在这种情况下,Z 应该完成,然后是任何 Y,最后是 X 上剩下的任何东西!
    • @cdaringe 很好,可能很重要,但我不确定“then”回调的调用应该如何与异步的其余部分相关联环境。
    猜你喜欢
    • 2021-07-29
    • 2016-02-16
    • 2023-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多