[英]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
的价值是什么? 你期望b
是10
还是你期望它是function(){return 10}
? 执行上面的代码后,您希望能够这样做:
console.log(b);
或者你认为你必须这样做:
console.log(b());
显然你会说b
是10
,而不是返回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()
但我们实际上并不叫b
或c
-我们让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.