为什么我需要在 Promise .then() 中闭包?

Pet*_*erT 4 javascript node.js promise

我一直在学习 Node (7.4.0) 中的 ES6 Promise,因为我想应用它们来处理串行通信。我做出了一个承诺,它是许多较小承诺的集合,允许我使用发送者、事件监听器和超时来序列化与设备的通信。但是,我不太理解 .then() 链接,因为我需要添加一些额外的闭包,这些闭包看起来与我在许多示例中看​​到的不同,这让我相信我误解了一些基本的东西。考虑这个函数(我删除了所有的prototype/this.code以使其更小):

function sendAck(command, ack, timeout) {
  return new Promise((resolve, reject) => {
    if (gReady === undefined) reject('not ready');
    // each of these three functions returns a promise
    let sendPromise = createSend(command);
    let ackPromise = createAck(ack);
    let timeoutPromise = createTimeout(timeout);
    // p1 = we hear a response, or timeout waiting for one
    let p1 = Promise.race([ackPromise, timeoutPromise]);
    // both p1 -and- send function need to resolve
    let p2 = Promise.all([p1, sendPromise]);
    p2.then(values => resolve(values)).catch(err => {
      localCleanup(); /// otherwise just return p2, but need to do this
      reject(err)
    });
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,当我尝试将一堆 sendAck() 与 then 链接起来时,我发现这种用法失败了,因为它们都同时执行:

sendAck('init', 'pass', 3000)
.then(sendAck('enable a', 'pass', 3000))
.then(sendAck('enable b', 'pass', 3000))
:
Run Code Online (Sandbox Code Playgroud)

所以我必须将每个都包装在一个闭包中才能使其工作,因为闭包是在 then() 上计算的,而不是由 JS 解释器计算的函数。这感觉就像我错过了一些非常重要的东西,因为它看起来很尴尬:

sendAck('init', 'pass', 3000)
.then(() => { return sendAck('enable a', 'pass', 3000) })
.then(() => { return sendAck('enable b', 'pass', 3000) })
:
Run Code Online (Sandbox Code Playgroud)

我很困惑,因为我在网上看到其他示例,其中 .then() 包含一个返回承诺的函数,例如......

.then(onHttpRequest)
Run Code Online (Sandbox Code Playgroud)

这显然不同于

.then(onHttpRequest())
Run Code Online (Sandbox Code Playgroud)

必须将 .then() 与闭包链接起来似乎很奇怪。我这样做是否正确,只是不习惯,还是我错过了什么?

提前致谢。

PT

编辑:如下所述,我的问题中没有闭包,只有匿名函数。

sle*_*man 5

这不是一个闭包

到目前为止,我在您的示例中没有看到任何结束。是的,我知道一些编程语言将匿名函数称为“闭包”,但以我的愚见,这些语言的开发人员只是对闭包是什么有误解。当然,在 javascript 中,我们不将匿名函数称为闭包,我们将其称为“匿名函数”。

这不是闭包问题,而是函数语义。

首先,忘记承诺。假设您有这段代码:

function a (x) {return x*2}

var b = a(5);
Run Code Online (Sandbox Code Playgroud)

现在,在任何编程语言中,无论是 Java、C++ 还是 javascript,您期望 的值是多少b?你期望b是这样10还是你期望它是这样function(){return 10}?执行上面的代码后,您是否希望能够执行以下操作:

console.log(b);
Run Code Online (Sandbox Code Playgroud)

或者您认为您必须这样做:

console.log(b());
Run Code Online (Sandbox Code Playgroud)

显然你会说bis 10,而不是返回的函数10。所有语言都是这样工作的,对吧?让我们把这个例子变得更复杂一些:

function a (x) {return x*2}

console.log(a(5));
Run Code Online (Sandbox Code Playgroud)

在上面的代码中,您希望console.log()打印function a(x){..}还是希望它打印10?显然它会打印 10。因为我们知道在编程语言中,当我们调用函数时,调用的结果不是函数本身,而是函数的返回值。请注意,上面的代码与以下代码完全相同:

function a (x) {return x*2}
var y = a(5);
console.log(y);
Run Code Online (Sandbox Code Playgroud)

如果我们想打印这个函数,我们会这样做:

console.log(a);
Run Code Online (Sandbox Code Playgroud)

在可以像传递数字或字符串一样传递函数的世界中,您需要更加了解函数和函数调用之间的区别。上面的代码中,a是一个函数。您可以像传递任何其他对象一样传递它。a()是一个函数调用,该函数调用的结果是该函数的返回值。

回到承诺

因此,在您的代码中,当您执行以下操作时:

sendAck('init', 'pass', 3000)
.then(sendAck('enable a', 'pass', 3000))
.then(sendAck('enable b', 'pass', 3000));
Run Code Online (Sandbox Code Playgroud)

这与做相同

// Functions below are async, they return immediately without waiting
// for data to be returned but returns promises that can wait for
// data in the future:
var a = sendAck('init', 'pass', 3000);
var b = sendAck('enable a', 'pass', 3000);
var c = sendAck('enable b', 'pass', 3000);

// Now we wait for return data:
a.then(b).then(c);
Run Code Online (Sandbox Code Playgroud)

请注意,虽然按.then()顺序解析,但它们sendAck是并行发送的,因为我们在调用下一个之前不会等待一个解析返回数据。

sendAck正如您所发现的,解决方案是在获得数据之前不要调用。所以我们需要这样做:

// We're not calling `sendAck` here, we're just declaring functions
// so nothing gets sent:
function a () {return sendAck('init', 'pass', 3000)}
function b () {return sendAck('enable a', 'pass', 3000)}
function c () {return sendAck('enable b', 'pass', 3000)}

// Now we can fire them in sequence:
a().then(b).then(c);
Run Code Online (Sandbox Code Playgroud)

请注意,我们通过调用来让事情滚动a(),但我们实际上并没有调用bc- 我们让它then为我们做。因为我们传递函数本身而不是调用它们,所以它们的函数体不会被执行。

当然,我们不需要创建命名函数。正如您自己发现的那样,我们可以轻松地使用匿名函数来实现此目的。所以上式可以改写为:

sendAck('init', 'pass', 3000)
.then(() => { return sendAck('enable a', 'pass', 3000) })
.then(() => { return sendAck('enable b', 'pass', 3000) });
Run Code Online (Sandbox Code Playgroud)