【问题标题】:Understanding javascript promises; stacks and chaining了解 javascript 承诺;堆栈和链接
【发布时间】:2015-07-03 10:00:56
【问题描述】:

我在 javascript 承诺方面遇到了一些问题,尤其是在堆栈链方面。

谁能向我解释这些不同实现之间的区别(如果有的话!)?

实施 1

var serverSidePromiseChain;
serverSidePromiseChain = async().then(function(response) {
    console.log('1', response);
    return response;
}).then(function(response) {
    console.log('2', response);
    return true;
}).then(function(response) {
    console.log('3', response); // response expected to be 'true'
    return async3();
}).then(function(response) {
    console.log('4', response);
    return async4();
})

实施 2

var serverSidePromiseChain;
serverSidePromiseChain = async().then(function(response) {
    console.log('1', response);
    return response;
});

serverSidePromiseChain.then(function(response) {
    console.log('2', response);
    return true;
})
serverSidePromiseChain.then(function(response) {
    console.log('3', response); // response expected to be 'true'
    return async3();
})
serverSidePromiseChain.then(function(response) {
    console.log('4', response);
    return async4();
})

实施 3

var serverSidePromiseChain;
serverSidePromiseChain = async().then(function(response) {
    console.log('1', response);
    return response;
});

serverSidePromiseChain = serverSidePromiseChain.then(function(response) {
    console.log('2', response);
    return true;
})
serverSidePromiseChain = serverSidePromiseChain.then(function(response) {
    console.log('3', response); // response expected to be 'true'
    return async3();
})
serverSidePromiseChain = serverSidePromiseChain.then(function(response) {
    console.log('4', response);
    return async4();
})

链的一部分返回一个值(步骤 2 中的“真”)这一事实是否会改变行为?承诺是否要求所有返回值都是异步承诺以保持行为?

【问题讨论】:

    标签: javascript promise chain


    【解决方案1】:

    您正在说明链接和分支之间的区别。链接将对多个异步操作进行排序,因此一个在前一个完成时开始,您可以链接任意数量的项目以一个接一个地排序。

    当一个触发器操作完成时,分支会连接多个异步操作以同时进行。

    实现 1 和 3 相同。他们被锁住了。实现 3 只是使用一个临时变量来链接,而实现 1 只是直接使用来自.then() 的返回值。执行上没有区别。这些.then() 处理程序将以串行方式调用。

    实施 2 不同。它是分支的,而不是链式的。因为所有后续的.then() 处理程序都附加到完全相同的serverSidePromiseChain 承诺,它们都只等待第一个承诺被解决,然后所有后续的异步操作都在同一时间运行(而不是像另一个两个选项)。


    这可能有助于理解这一点,深入了解它如何与 Promise 一起工作。

    当你这样做时(场景 1 和 3):

    p.then(...).then(...)
    

    发生的情况如下:

    1. 解释器获取您的p 变量,在其上找到.then() 方法并调用它。
    2. .then() 方法只是存储它传递的回调,然后返回一个新的 Promise 对象。此时它不会调用它的回调。这个新的 Promise 对象与原始 Promise 对象和它存储的回调绑定。在两者都满足之前,它不会解决。
    3. 然后调用新返回的 Promise 上的第二个 .then() 处理程序。同样,该承诺上的 .then() 处理程序仅存储 .then() 回调,它们尚未执行。
    4. 然后在未来的某个时候,原来的 Promise p 会被它自己的异步操作解决。当它被解析时,它会调用它存储的任何resolve 处理程序。其中一个处理程序将是对上述链中第一个 .then() 处理程序的回调。如果该回调运行完成并且不返回任何内容或返回静态值(例如,不返回承诺本身),那么它将解析它创建的承诺,以便在首次调用 .then() 后返回。当该 promise 被解析后,它将调用上面第二个 .then() 处理程序安装的解析处理程序,依此类推。

    当你这样做时(场景 2):

    p.then();
    p.then();
    

    这里的一个promise p 存储了来自.then() 调用的解析处理程序。当最初的承诺 p 得到解决时,它将调用两个 .then() 处理程序。如果 .then() 处理程序本身包含异步代码并返回承诺,这两个异步操作将同时进行(类似并行行为),而不是像场景 1 和 3 中那样按顺序进行。

    【讨论】:

    • “实现 2 不同。它是分支的,而不是链式的。因为所有后续的 .then() 处理程序都附加到完全相同的 serverSidePromiseChain 承诺,它们都只等待第一个承诺被解决”是(对我来说)最简洁明了的答案。说得好。谢谢!
    • 您说“当同一操作完成时,分支会连接多个操作以并行运行。”这是一种误导,因为它可以被解读为 handlers 并行运行,这是不正确的(处理程序在同一个线程中串行运行,以相同的顺序运行相应的thens 被调用)。类似地,最后一句“这两个 .then() 处理程序将并行运行”更加明显地错误陈述:不,.then() 处理程序不会并行运行,但它们安排的异步工作可能会。
    • @DonHatch - 我已经调整了一些措辞,但你在这里有点狡辩。这里的概念是,分支允许所有异步操作同时进行(通常称为“并行”),而链接则强制执行严格的串行操作,因此在第一个异步操作解决之前,甚至不会启动第二个异步操作.是的,JS 是单线程的,所以在任何情况下实际上都没有 JS 代码并行运行,但是异步操作本身是同时进行的,这对于异步操作来说是类似并行的行为,而不是串行行为。
    • 谢谢。我确实认为这是一个重要的区别,而且不一定很明显,尤其是对于那些使用你的答案作为材料来了解和巩固我对 javascript 和 promise 工作方式的理解的人(比如我自己)。谢谢你逗我。现在我觉得措辞很好。
    【解决方案2】:

    实现 1 和 3 似乎是等效的。

    在实现 2 中,最后 3 个 .then() 函数都作用于同一个承诺。 .then() 方法返回一个新的承诺。已履行的承诺的价值无法更改。见Promises/A+ 2.1.2.2。您在实施 2 中的评论(该响应预计为真)表明您期望其他情况。不,response 不会是真的(除非那是原始承诺的价值)。

    让我们试一试。运行以下代码 sn -p 看看有什么区别:

    function async(){ return Promise.resolve("async"); }
    function async3(){ return Promise.resolve("async3"); }
    function async4(){ return Promise.resolve("async4"); }
    
    
    function implementation1() {
      logContainer = document.body.appendChild(document.createElement("div"));
      console.log("Implementation 1");
      var serverSidePromiseChain;
      serverSidePromiseChain = async().then(function(response) {
        console.log('1', response);
        return response;
      }).then(function(response) {
        console.log('2', response);
        return true;
      }).then(function(response) {
        console.log('3', response); // response expected to be 'true'
        return async3();
      }).then(function(response) {
        console.log('4', response);
        return async4();
      });
    }
    
    function implementation2() {
      logContainer = document.body.appendChild(document.createElement("div"));
      console.log("Implementation 2");
      var serverSidePromiseChain;
      serverSidePromiseChain = async().then(function(response) {
        console.log('1', response);
        return response;
      });
      serverSidePromiseChain.then(function(response) {
        console.log('2', response);
        return true;
      });
      serverSidePromiseChain.then(function(response) {
        console.log('3', response); // response expected to be 'true'
        return async3();
      });
      serverSidePromiseChain.then(function(response) {
        console.log('4', response);
        return async4();
      });
    }
    
    function implementation3() {
      logContainer = document.body.appendChild(document.createElement("div"));
      console.log("Implementation 3");
      var serverSidePromiseChain;
      serverSidePromiseChain = async().then(function(response) {
        console.log('1', response);
        return response;
      });
      serverSidePromiseChain = serverSidePromiseChain.then(function(response) {
        console.log('2', response);
        return true;
      });
      serverSidePromiseChain = serverSidePromiseChain.then(function(response) {
        console.log('3', response); // response expected to be 'true'
        return async3();
      });
      serverSidePromiseChain = serverSidePromiseChain.then(function(response) {
        console.log('4', response);
        return async4();
      });
    }
    
    var logContainer;
    var console = {
      log: function() {
        logContainer.appendChild(document.createElement("div")).textContent = [].join.call(arguments, ", ");
      }
    };
    
    onload = function(){
      implementation1();
      setTimeout(implementation2, 10);
      setTimeout(implementation3, 20);
    }
    body > div {
      float: left;
      font-family: sans-serif;
      border: 1px solid #ddd;
      margin: 4px;
      padding: 4px;
      border-radius: 2px;
    }

    【讨论】:

      【解决方案3】:

      实现#1 和#3 是等价的。实现 #2 不同,因为那里没有链,所有回调都将在同一个 Promise 上执行。

      现在让我们稍微讨论一下承诺链。 specs 告诉你:

      2.2.7 then 必须返回一个承诺
      2.2.7.1 如果onFulfilled 或onRejected 返回值x,则运行Promise Resolution Procedure [[Resolve]](promise2, x)
      2.3.3 如果x是一个promise,采用它的状态

      基本上,在 Promise 上调用 then 会返回另一个 promise,它会根据 callback return value 解决/拒绝。在您的情况下,您将返回标量值,然后将其沿链传播到下一个承诺。

      在您的特定情况下,会发生以下情况:

      • #1:你有 7 个 promise(async 调用加上 4 个then,加上两个来自 async3()/async4),serverSidePromiseChain 将指向 then 返回的最后一个 promise。现在如果async() 返回的承诺永远不会被解决/拒绝,那么serverSidePromiseChain 也将处于相同的情况。如果该承诺也未解决/拒绝,则与 async3()/async4() 相同
      • #2:then 在同一个 Promise 上被多次调用,会创建额外的 Promise,但它们不会影响应用程序的流程。一旦async()返回的promise被解析,所有的回调都会被执行,回调返回的将被丢弃
      • #3:这相当于#1,只是现在你明确地传递了创建的 Promise。当返回的promise async() 被解析时,第一个回调将被执行,它使用true 解析下一个promise,第二个回调将相同,第三个将有机会将链转换为失败的, 如果 async3() 的 promise 被拒绝,与返回 async4() 的 promise 的回调相同。

      Promise 链最适合实际的异步操作,操作依赖于前一个操作的结果,你不想写很多胶水代码,你也不想接触到@987654322 @。

      我在博客上写了一系列关于promise的文章,一篇描述promise链式特性的文章可以在here找到;这篇文章是针对ObjectiveC的,但是原理是一样的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-01-27
        • 2016-11-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多