理解JavaScript的Promise;堆栈和链式调用

7

我遇到了一些与JavaScript Promise有关的问题,特别是在堆叠链中。

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

实现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个回答

29
你正在说明链式和分支式之间的区别。链式会将多个异步操作依次排列,使得一个在前一个完成时开始,而你可以链接任意数量的项以便一个接一个地进行排序。
当一个触发操作完成时,分支式将多个异步操作连接到同一时间飞行。实现1和3是相同的。它们是被链接的。实现3只是使用一个临时变量进行链接,而实现1直接使用.then()的返回值进行链接。执行上没有任何区别。这些.then()处理程序将按顺序调用。
实现2则不同。它是分支式而不是链式。因为所有后续的.then()处理程序都附加到完全相同的serverSidePromiseChain承诺上,所以它们只等待第一个承诺被解决,然后所有后续的异步操作都会同时进行(不像其他两种选项那样是串行的)。
深入了解承诺如何工作可能会有所帮助。
当你执行(场景1和3):
p.then(...).then(...)

发生的过程如下:

  1. 解释器获取你的p变量,找到它的.then()方法并调用它。
  2. .then()方法只是存储传递给它的回调函数,然后返回一个新的promise对象。此时它不会调用其回调函数。这个新的promise对象与原始promise对象和它存储的回调函数相关联。只有在两者都得到满足之后,它才会被解决。
  3. 然后调用那个新返回的promise上的第二个.then()处理程序。同样,该promise上的.then()处理程序只是存储.then()回调函数,但它们尚未执行。
  4. 然后,在将来的某个时间,原始的promise p由其自己的异步操作解决。当它得到解决时,它将调用任何存储的resolve处理程序。其中一个处理程序将是上述链中第一个.then()处理程序的回调函数。如果回调函数运行完成并返回空值或静态值(例如不返回另一个promise),则它将解决创建并在第一次调用.then()后返回的promise。当该promise解决时,它将调用由上面第二个.then()处理程序安装的解决程序以此类推。

当你这样做时(方案2):

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

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


2
实现方式2不同。它是分支的,而不是链式的。因为所有后续的.then()处理程序都附加到完全相同的serverSidePromiseChain承诺上,所以它们只等待第一个承诺被解决。 - Federico
1
你说“分支将多个操作连接起来,以便在完成相同操作时所有操作都可以并行运行。”这是误导性的,因为它可以被理解为处理程序并行运行,这是不正确的(处理程序在同一线程中串行运行,按照调用相应then的顺序)。同样,最后一句话“这两个.then()处理程序将并行运行”更明显地表述错误:不,.then()处理程序不会并行运行,但它们安排的异步工作可能会。 - Don Hatch
@DonHatch - 我稍微调整了措辞,但你在这里有些挑剔。这里的概念是分支允许异步操作同时进行(通常称为“并行”),而链接则强制执行严格的串行操作,因此第二个异步操作甚至不会开始,直到第一个操作已解决。是的,JS是单线程的,因此在任何情况下都没有JS代码实际上运行并行,但异步操作本身是同时进行的,这对于异步操作而言是类似并行行为而不是串行行为。 - jfriend00
谢谢。我认为这是一个重要的区别,不一定明显,特别是对于像我这样将您的答案作为学习 JavaScript 和 Promise 工作原理材料并巩固自己理解的人来说。感谢您的耐心回答。现在措辞看起来非常好。 - Don Hatch

3
实现方式1和3是等价的。实现方式2不同,因为它没有链式结构,所有回调函数都将在同一个Promise上执行。
现在让我们稍微讨论一下Promise链。规范告诉我们:
2.2.7 then 必须返回一个promise
2.2.7.1 如果 onFulfilledonRejected 返回值 x,则运行 Promise 解析过程 [[Resolve]](promise2, x)
2.3.3 如果 x 是一个promise,则采用其状态
基本上,在Promise上调用then会返回另一个promise,该promise基于回调函数返回值得到解决/拒绝。在你的情况下,你返回标量值,然后这些值被传播到下一个promise中。
在你的特定情况下,以下是发生的事情:
  • #1: 你有7个承诺(async调用加上4个then,再加上来自async3()/async4的两个),serverSidePromiseChain将指向由then返回的最后一个承诺。现在,如果由async()返回的承诺永远不被解决/拒绝,则serverSidePromiseChain也将处于相同的情况。如果该承诺也未被解决/拒绝,那么async3()/async4()也是一样的。
  • #2: 在同一承诺上多次调用then,会创建额外的承诺,但它们不会影响应用程序的流程。一旦由async()返回的承诺得到解决,所有回调函数都将被执行,回调函数返回的内容将被丢弃。
  • #3: 这等同于#1,只是现在你显式传递了创建的承诺。当由async()返回的承诺得到解决时,第一个回调函数将被执行,它使用true解决下一个承诺,第二个回调函数也会这样做,第三个回调函数将有机会将链转换为失败的链,如果async3()的承诺被拒绝,async4()的承诺也是如此。
承诺链最适合实际的异步操作,其中操作依赖于前一个操作的结果,您不想编写大量粘合代码,也不想陷入回调地狱。我在我的博客上写了一系列关于Promise的文章,其中一篇描述了Promise的链接特性,可以在这里找到;这篇文章是针对ObjectiveC的,但原则是相同的。

1

实现方式1和3看起来是等价的。

在实现方式2中,最后三个.then()函数都作用于同一个 promise 上。 .then()方法返回一个新的 promise。被兑现的 promise 的值不能被更改。请参阅Promises/A+ 2.1.2.2。您在实现方式2中的评论中,表示预期 response 为 true,这表明您期望另外一种结果。不,除非那是原始 promise 的值,否则response不会是 true。

让我们试一下。运行下面的代码片段查看差异:

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


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