[英]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.