簡體   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