繁体   English   中英

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

[英]why do i need a closure in a Promise .then()?

我一直在学习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()包含一个返回一个promise的函数,比如......

.then(onHttpRequest)

这明显不同于

.then(onHttpRequest())

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

提前致谢。

PT

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

那不是关闭

到目前为止,我没有在你的例子中看到任何关闭。 是的,我知道一些编程语言将匿名函数称为“闭包”,但我认为这些语言的开发人员对闭包的含义存在误解。 当然在javascript中我们不调用匿名函数闭包,我们称之为“匿名函数”。

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

首先,忘掉承诺。 假设你有这段代码:

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

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

正如您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) });

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

你也可以缩短它

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

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

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

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

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

这将在它尝试将任何内容传递到.then(..)之前进行评估

如果你不想使用箭头函数,你通常会写类似的东西

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

这实际上是相同的,但与箭头功能的范围不同

如果将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的答案中较短的语法。 我不认为你的方法/用法存在任何问题。

你也可以缩短

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM