了解JavaScript的承诺; 堆栈和链接
我已经遇到了一些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”)会改变行为吗? 承诺是否要求所有返回的值是asynchronous承诺以保持行为?
您正在说明链接和分支之间的不同之处。 链接序列多个asynchronous操作,所以一个开始时,前一个完成,你可以链任意数量的项目顺序一个接一个。
当一个触发操作完成时,分支挂接多个asynchronous操作,以便同时处于运行状态。
实现1和3是相同的。 他们被链接。 实现3只是使用一个临时variables来链接,而实现1只是直接使用.then()
的返回值。 执行没有区别。 这些.then()
处理程序将以串行方式调用。
实现2是不同的。 它是分支的,而不是链接的。 由于所有后续的.then()
处理程序都附加到完全相同的serverSidePromiseChain
承诺中,它们都只会等待第一个要解决的承诺,然后所有后续的asynchronous操作都将同时在运行(而不是像其他两个选项)。
了解这一点可能会有所帮助,将其中一个层次下降到承诺的工作方式。
当你做(场景1和3):
p.then(...).then(...)
会发生什么如下:
- 解释器把你的
p
variables,find.then()
方法并调用它。 -
.then()
方法只存储它传递的callback,然后返回一个新的promise对象。 这个时候不会调用它的callback函数。 这个新的承诺对象与最初的承诺对象和它所存储的callback都有关系。 直到双方都满意后才能解决。 - 然后在那个新返回的promise上调用第二个
.then()
处理程序。 再一次,那个promise上的.then()
处理程序只是存储.then()
callback函数,而且还没有执行。 - 那么在将来的某个时候,原来的承诺
p
会通过自己的asynchronous操作来解决。 当它得到解决后,它会调用它存储的任何resolve
处理程序。 其中一个处理程序将是上述链中第一个.then()
处理程序的callback。 如果该callback运行到完成状态,并返回无或静态值(例如,本身不返回承诺),则它将parsing在首次调用.then()
之后创build的承诺。 当这个承诺解决后,它会调用上面的第二个.then()
处理程序安装的parsing处理程序等等。
当你这样做(场景2)时:
p.then(); p.then();
这里的一个承诺p
已经存储了来自.then()
调用的parsing处理程序。 当原始的promise被parsing的时候,它会调用两个.then()
处理程序。 如果.then()
处理程序本身包含asynchronous代码并返回promise,则这两个asynchronous操作将同时处于运行状态(类似并行的行为),而不是如场景1和3中的顺序。
实现#1和#3是等同的。 实现#2不同,因为那里没有链,所有的callback都将在相同的承诺上执行。
现在我们来讨论一下诺言链。 规格说明:
2.2.7
then
必须返回一个承诺
onFulfilled
如果onFulfilled
或onRejected
返回值x
,则运行Promise Resolution Procedure[[Resolve]](promise2, x)
2.3.3如果x
是一个承诺,则采用它的状态
then
基本上调用一个承诺返回另一个promise
,根据callback return value
到解决/拒绝。 在你的情况下,你正在返回标量值,然后传播到下一个承诺。
在你的具体情况下,会发生什么情况:
- #1:你有7个承诺(
async
调用加4then
再加上两个来自async3()
/async4
),serverSidePromiseChain
将指向最后返回的promise。 现在,如果由async()
返回的promise始终不被parsing/拒绝,那么serverSidePromiseChain
也将处于相同的情况。 与async3()
/async4()
如果承诺也没有解决/拒绝 - #2:
then
在相同的承诺上被多次调用,额外的承诺被创build,但是它们不影响应用程序的stream程。 一旦由async()
返回的promise将被parsing,所有的callback函数将被执行,callback函数返回的将被丢弃 - #3:这相当于#1只有你明确地传递了创build的承诺。 当parsing了返回的promise后,第一个callback函数将被执行,第二个callback函数将返回
true
,第三个callback函数将返回true
,第三个callback函数将会有机会转换为失败的ifasync3()
的承诺被拒绝,与返回async4()
的承诺的callback一样。
Promise链最适合实际的asynchronous操作,其操作取决于前一个操作的结果,而且您不想编写大量的胶水代码,也不希望触及到callback地狱 。
我在博客上写了一系列关于承诺的文章,其中一篇描述了承诺的链接特征。 这篇文章是针对ObjectiveC的,但是原理是一样的。
实施1和3似乎是等同的。
在实现2中,最后的3个.then()
函数全都按照相同的承诺行事。 .then()
方法返回一个新的承诺。 兑现承诺的价值不能改变。 见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; }