为什么我需要一个Promise .then()的闭包?

时间:2017-03-25 17:44:39

标签: javascript node.js promise

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

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

现在,当我尝试将一堆sendAck()链接起来时,我发现这个用法失败了,因为它们都是立即执行的:

sendAck('init', 'pass', 3000)
.then(sendAck('enable a', 'pass', 3000))
.then(sendAck('enable b', 'pass', 3000))
:

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

sendAck('init', 'pass', 3000)
.then(() => { return sendAck('enable a', 'pass', 3000) })
.then(() => { return sendAck('enable b', 'pass', 3000) })
:

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

.then(onHttpRequest)

明显不同于

.then(onHttpRequest())

使用闭包链接.then()似乎很奇怪。我这样做是否正确而且不习惯,或者我错过了什么?

提前致谢。

PT

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

4 个答案:

答案 0 :(得分:2)

.then(sendAck('enable a', 'pass', 3000))同步执行sendAck函数,启动该承诺,并将结果传递给.then().then()将尝试稍后调用该结果当承诺链继续,但你已经调用它,所以它不会等待异步承诺链。

您也可以将其缩短为

.then(() => sendAck('enable a', 'pass', 3000))

或者你可以传入一个绑定函数引用,promise将在稍后调用:

.then(sendAck.bind(null, 'enable a', 'pass', 3000))

答案 1 :(得分:1)

当您将函数传递给.then(..)方法时,实际上是在告诉js评估(或调用)函数,因为您已经使用了括号并添加了参数:

sendAck('enable a', 'pass', 3000); // runs immediately, then passes value to .then(..)

在尝试将任何内容传递到.then(..)

之前,将对此进行评估

如果您不想使用箭头功能,通常会写一些类似

的内容
.then(function () {
    sendAck('enable a', 'pass', 3000);
});

实际上是相同的,但与箭头函数

的范围不同

答案 2 :(得分:1)

如果您将sendAck函数变为高阶函数,那么您可以像原来一样使用.then

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

sendAck('init', 'pass', 3000)()
.then(sendAck('enable a', 'pass', 3000))
.then(sendAck('enable b', 'pass', 3000));

或者,您可以使用Andy Ray's answer中较短的语法。我不认为你的方法/用法存在任何问题。

你也可以缩短

p2.then(values => resolve(values)).catch(err => {
    localCleanup(); /// otherwise just return p2, but need to do this
    reject(err)
});

为:

p2.then(resolve).catch(err => {
    localCleanup();
    reject(err);
});

答案 3 :(得分:1)

那不是关闭

到目前为止,我没有看到你的例子中有任何关闭。是的,我知道一些编程语言称为匿名函数"闭包"但在我看来,这些语言的开发人员只是对闭包的误解。当然在javascript中我们不会调用匿名函数闭包,我们称之为#34;匿名函数"。

它不是一个闭包问题,它的函数语义。

首先,忘掉承诺。让我们说你有这段代码:

function a (x) {return x*2}

var b = a(5);

现在,在任何编程语言中,无论是Java还是C ++或javascript,您期望b的价值是什么?您希望b10还是期望function(){return 10}?执行上面的代码后,您希望能够执行此操作:

console.log(b);

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

console.log(b());

显然,您说b10,而不是返回10的函数。所有语言都是这样的吗?所以让我们让这个例子更复杂一点:

function a (x) {return x*2}

console.log(a(5));

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

function a (x) {return x*2}
var y = a(5);
console.log(y);

如果我们想要打印我们要执行的功能:

console.log(a);

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

回到承诺

因此,在您的代码中,当您这样做时:

sendAck('init', 'pass', 3000)
.then(sendAck('enable a', 'pass', 3000))
.then(sendAck('enable b', 'pass', 3000));

与做

相同
// 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);

请注意,虽然.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);

请注意,我们通过致电a()来推动这项活动,但我们实际上并没有致电bc - 我们让then为我们做了。因为我们自己传递功能而不是称他们为自己的身体而不会被执行。

当然,我们不需要创建命名函数。正如您自己发现的那样,我们可以轻松地使用匿名功能。所以上面的内容可以改写为:

sendAck('init', 'pass', 3000)
.then(() => { return sendAck('enable a', 'pass', 3000) })
.then(() => { return sendAck('enable b', 'pass', 3000) });