理解javascript承诺;堆叠和链接

时间:2015-04-24 17:14:08

标签: javascript promise chain

我遇到了一些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中的'true')会改变行为吗?承诺是否要求所有返回的值都是异步承诺以保持行为?

3 个答案:

答案 0 :(得分:23)

您正在说明链接和分支之间的不同。链接wil序列多个异步操作,因此当前一个完成时开始,你可以链接任意数量的项目,依次排序。

当一个触发操作完成时,分支将多个异步操作挂钩到同一时间。

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

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

了解这一点可能有助于将一个级别深入了解如何使用promises。

当你这样做时(方案1和3):

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

发生的情况如下:

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

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

    此承诺p此处已存储.then()次调用的解析处理程序。当原始承诺p得到解决后,它将调用两个.then()处理程序。如果.then()处理程序本身包含异步代码并返回promises,则这两个异步操作将同时处于飞行状态(类似并行行为),而不是按照场景1和3中的顺序执行。

答案 1 :(得分:3)

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

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

  

2.2.7 then必须返回承诺   2.2.7.1如果onFulfilledonRejected返回值x,请运行承诺解决程序[[Resolve]](promise2, x)
  2.3.3如果x是承诺,则采用其状态

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

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

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

Promise链最适合实际的异步操作,其中操作依赖于前一个操作的结果,并且您不想编写大量的粘合代码,并且您也不希望到达{{ 3}}

我在博客上写了一系列关于承诺的文章,其中一篇描述了承诺的链接特征callback hell;该文章针对ObjectiveC,但原则是相同的。

答案 2 :(得分:1)

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

在实现2中,最后3个.then()函数都对相同的承诺起作用。 .then()方法返回一个新的promise。履行承诺的价值无法改变。见Promises/A+ 2.1.2.2。您在实现2中的评论,该响应预计为真,表示您不希望如此。不,response不是真的(除非那是原始承诺的价值)。

我们试一试吧。运行以下代码段以查看差异:

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;
}