Promise中多個回撥函式之間的資料傳遞

前端深夜告解室發表於2017-08-18

譯文地址:2ality.com/2017/08/pro…
譯者:bryan.yang,美團金融FE

  在基於Promise的程式碼中,會有很多的回撥函式,每個回撥函式都有自己的變數作用域。如果你想在這些回撥函式之間共享資料該怎麼辦呢?以下這篇文章將會給出幾種幾種解決方法。

1.引出問題

  以下面這段Promise回撥程式碼中常見的問題為例:在第一個Promise回撥中(line A)宣告瞭變數connection,而這個變數將會在接下來的兩個回撥中(line B and line C)引用。

db.open()
.then(connection => { // (A)
    return connection.select({ name: 'Jane' });
})
.then(result => {
    // 獲取結果
    // 用 `connection` 進行更多的查詢操作 (B)
})
···
.catch(error => {
    // 捕獲錯誤
})
.finally(() => {
    connection.close(); // (C)
});複製程式碼

  在程式碼中我們使用了Promise.prototype.finally(),它是ES新新增的一個特性,工作原理與try語句塊中的finally類似。

2.有副作用的解決方式

  第一種解決方式就是直接將共享的資料儲存在外層的作用域的變數中(line A),然後在每個回撥中使用。

let connection; // (A)
db.open()
.then(conn => {
    connection = conn;
    return connection.select({ name: 'Jane' });
})
.then(result => {
    // 獲取結果
    // 使用 `connection` 執行更多的查詢操作 (B)
})
···
.catch(error => {
    // 捕獲錯誤
})
.finally(() => {
    connection.close(); // (C)
});複製程式碼

  不難看出,由於connection變數位於外層域中,所以在B與C的回撥中都能夠使用這個變數。

3.巢狀作用域的方式

  在介紹這個方式之前我們先來看一下以同步的方式實現的程式碼:

try {
    const connection = await db.open();
    const result = await connection.select({ name: 'Jane' });
    ···
} catch (error) {
    // 捕獲錯誤
} finally {
    connection.close();
}複製程式碼

  在同步的方式中,我們要想共用connection這個資料,通常也是在外層作用域中宣告一個變數來儲存,程式碼如下:

const connection = await db.open();// (A)
try {
    const result = await connection.select({ name: 'Jane' });
    ···
} catch (error) {
    // 捕獲錯誤
} finally {
    connection.close();        
}複製程式碼

  其實我們可以使用Promise實現類似的效果 ---- 巢狀Promise,程式碼如下:

db.open() // (A)
.then(connection => { // (B)
    return connection.select({ name: 'Jane' }) // (C)
    .then(result => {
        // 獲取結果
        // 使用 `connection` 執行更多查詢操作
    })
    ···
    .catch(error => {
        // 捕獲錯誤
    })
    .finally(() => {
        connection.close();
    });    
})複製程式碼

在上面這段程式碼中有兩個Promise:

  • 第一個Promise從第A行開始。變數connection是函式open()執行的非同步返回結果。
  • 第二個Promise被巢狀在then方法的回撥中(line B)。從第C行開始。 注意在位置C中的return語句, 它確保了這兩個Promise能夠正確地一起使用。
    通過巢狀,第二個Promise中的所有回撥都能夠引用connection變數。

  可能你已經注意到,在同步和巢狀Promise的實現方式中,db.open()這個同步方法丟擲的錯誤並不會被捕獲。這篇文章A follow-up blog post on Promise.try()將會告訴你如何來完善這段非同步程式碼。在同步的程式碼中你可以將db.open()使用try語句來捕獲相關異常。

4.多值返回

  下面的這些程式碼將會演示多個回撥之間傳遞資料的另一種方法。當然,這些程式碼並不總是可用的。實際上,你並不能用這段程式碼來連線你正在執行的資料庫。我們先來看一個能夠執行的栗子。
  正如我們正在解決的問題一樣:在這段程式碼中,位於位置A的Promise中的intermediate變數需要在位置B中引用。

return asyncFunc1()
.then(result1 => { // (A)
    const intermediate = ···;
    return asyncFunc2();
})
.then(result2 => { // (B)
    console.log(intermediate);
    ···
});複製程式碼

  我們可以通過在第一個回撥中使用Promise.all()傳遞多個值給第二個回撥的方式來解決這個問題:

return asyncFunc1()
.then(result1 => {
    const intermediate = ···;
    return Promise.all([asyncFunc2(), intermediate]); // (A)
})
.then(([result2, intermediate]) => {
    console.log(intermediate);
    ···
})複製程式碼

  需要注意的是在第一個回撥中直接返回一個陣列並不能生效,因為如果直接返回一個陣列,then()回撥將會接收到一個由Promise和一個普通值組成的陣列,這並不是我們想要的結果。這裡使用Promise.all(),它將會使用Promise.resolve()方法將陣列中的每一個元素轉換成Promise,然後執行轉換之後的Promise,並將結果放到一個普通陣列中(如果每個Promise執行成功)。這個方法也有是侷限的,你不能把你想要共享的資料傳遞到catch和finally語句塊中。
  最後這種方法將會從Promise.all()支援Object引數的版本受益。然後你將可以標記每一個返回值了。

相關文章