我們所瞭解的Promise

瞭解為什麼以及如何在Node。js中使用promises

我們所瞭解的Promise

回撥是在JavaScript中處理非同步程式碼的最簡單的機制。 然而,原始回撥犧牲了開發人員在同步程式碼中熟悉的控制流,異常處理和函式語義:

// Asynchronous operations return no meaningful valuevar noValue = fs。readFile(‘file1。txt’, function(err, buf) { // Errors are explicitly handled every time if (err) return handleErr(err) fs。readFile(‘file2。txt’, function(err2, buf2) { if (err2) return handleErr(err2) data。foo。baz = ‘bar’ // Exceptions like this ReferenceError are not caught // Sequential operations encourage heavy nesting fs。readFile(‘file3。txt’, function(err3, buf3) { if (err3) return handleErr(err3) }) }) })

Promise提供了一種方法來恢復控制:

更強大的控制流程

更好的異常處理

函數語言程式設計語義

儘管如此,Promise可能會令人困惑,因此你可能已經將它們寫下來或直接跳過async / await,這為JavaScript提供了新的Promise語法。

但是,瞭解Promise如何在基本層面上發揮作用和行為將有助於您充分利用它們。 在本文中,我們將介紹Promise的基礎知識,包括它是什麼,如何建立它,以及如何最有效地使用它。

抽像的

Promise

首先,讓我們來看看Promise的行為:它是什麼以及它如何有用? 然後我們將討論如何建立和使用Promise。

什麼是Promise? 我們來看一個定義:

Promise是非同步程式設計的抽象。 它是一個物件,代理返回值或由必須進行非同步處理的函式丟擲的異常。 - JSJ的Kris Kowal

Promise物件的核心元件是它的then方法。 then方法是如何從非同步操作中獲取返回值(稱為實現值)或丟擲異常(稱為拒絕原因)。 然後將兩個可選的回撥作為引數,我們將呼叫onFulfilled和onRejected:

let promise = doSomethingAync()promise。then(onFulfilled, onRejected)

當Promise解析時(非同步處理已完成),onFulfilled和onRejected觸發器。 其中一個功能將觸發,因為只有一個是可能的。

Promise回撥

鑑於這些Promise的基本知識,讓我們看一下熟悉的非同步Node。js回撥:

readFile(function(err, data) => { if (err) return console。error(err) console。log(data)})

如果我們的readFile函式返回了一個promise,我們將編寫相同的邏輯:

let promise = readFile()promise。then(console。log, consoler。error)

乍一看,它似乎改變了美學。 但是,我們現在可以訪問表示非同步操作(promise)的值。 我們可以在程式碼中傳遞Promise,就像JavaScript中的任何其他值一樣。 無論非同步操作是否已完成,任何有權訪問promise的人都可以使用它。 我們還保證非同步操作的結果不會以某種方式改變,因為promise將resolve一次(即fulfilled 或 rejected)。

將其視為一個需要兩個回撥函式(onFulfilled和onRejected)的函式是有幫助的,但是作為一個函式,它展開了揭示非同步操作發生的情況。 任何有權訪問許可權的人都可以使用它來開啟它。

Promise的

連結

與巢狀

then方法返回Promise

let promise = readFile()let promise2 = promise。then(readAnotherFile, console。error)

我們所瞭解的Promise

此promise表示其onFulfilled或onRejected處理程式的返回值(如果已指定),promise將代理觸發處理程式:

let promise = readFile()let promise2 = promise。then( function(data) { return readAnotherFile() // If readFile was successful, let‘s readAnotherFile }, function(err) { console。error(err) // If readFile was unsuccessful, let’s log it but still readAnotherFile return readAnotherFile() })promise2。then(console。log, console。error) // The result of readAnotherFile

既然then返回一個Promise,這意味著Promise可以連結在一起,以避免回撥地獄的深層巢狀:

readFile() 。then(readAnotherFile) 。then(doSomethingElse) 。then(。。。)

Promse 與同步函式

Promise可以模擬同步功能。 使用return來繼續而不是呼叫另一個函式。 前面的示例返回readAnotherFile()以表示在readFile()之後要執行的操作。

如果您返回一個promise,它將在非同步操作完成時發出下一個訊號。 您還可以返回任何其他值,下一個onFulfilled將獲取該值作為引數:

readFile() 。then(function (buf) { return JSON。parse(buf。toString()) }) 。then(function (data) => { // Do something with `data` })

Promise的異常處理

您還可以使用throw關鍵字以及try / catch。 這可能是Promise最強大的功能之一。 例如,請考慮以下同步程式碼:

try { doThis() doThat() } catch (err) { console。error(err) }

在這個例子中,如果doThis()或doThat()會丟擲錯誤,我們將捕獲並記錄錯誤。 由於try / catch塊允許分組操作,因此我們可以避免顯式處理每個操作的錯誤。 我們可以使用promises非同步執行同樣的操作:

doThisAsync() 。then(doThatAsync) 。then(undefined, console。error)

如果doThisAsync()不成功,則其promise將拒絕,然後在鏈中使用onRejected處理程式觸發。 在這種情況下,它是console。error函式。 和try / catch塊一樣,doThatAsync()永遠不會被呼叫。 這是對原始回撥的改進,您必須在每個步驟中明確處理錯誤。

但是,任何丟擲的異常,無論是隱式還是顯式的 - 其回撥也在promises中處理:

doThisAsync() 。then(function(data) { data。foo。baz = ‘bar’ // Throws a ReferenceError as foo is not defined }) 。then(undefined, console。error)

這裡,引發的ReferenceError觸發鏈中的下一個onRejected處理程式。 很簡單! 當然,這也適用於顯式丟擲:

doThisAsync() 。then(function(data) { if (!data。baz) throw new Error(‘Expected baz to be there’) }) 。catch(console。error) // The catch(fn) is shorthand for 。then(undefined, fn)

錯誤處理的重要說明

如前所述,promises模仿try / catch語義。 在try / catch塊中,可以透過從不顯式處理它來掩蓋錯誤:

try { throw new Error(‘Never will know this happened’)} catch (e) {}

Promise也是一樣

readFile()。then(function(data) { throw new Error(‘Never will know this happened’)})

要公開掩碼錯誤,解決方案是使用簡單的。catch(onRejected)子句結束promise鏈:

readFile() 。then(function(data) { throw new Error(‘Now I know this happened’) }) 。catch(console。error)

構造Promise

您也可以使用Promise建構函式建立一個promise。 讓我們轉換相同的fs。readFile方法來返回promises而不使用util。promisify:

const fs = require(‘fs’)function readFile(file, encoding) { return new Promise(function(resolve, reject) { fs。readFile(file, encoding, function(err, data) { if (err) return reject(err) // Rejects the promise with `err` as the reason resolve(data) // Fulfills the promise with `data` as the value }) })}let promise = readFile(‘myfile。txt’)promise。then(console。log, console。error)

建立支援回撥和Promise的API

我們已經看到了兩種將回調程式碼轉換為promise程式碼的方法。 您還可以建立提供promise和回撥介面的API。 例如,讓我們將fs。readFile轉換為支援回撥和承諾的API:

const fs = require(‘fs’)function readFile(file, encoding, callback) { if (callback) return fs。readFile(file, encoding, callback) // Use callback if provided return new Promise(function(resolve, reject) { fs。readFile(file, encoding, function(err, data) { if (err) return reject(err) resolve(data) }) })}

如果存在回撥,則使用標準Node樣式(錯誤,結果)引數觸發它。

readFile(‘myfile。txt’, ‘utf8’, function(er, data) { // 。。。})

使用promises進行並行操作

我們已經討論過順序非同步操作。 對於並行操作,ES6 / 2015提供了Promise。all方法,該方法接受一系列promise並返回一個新的promise。 所有操作完成後,新Promise即告完成。 如果任何操作失敗,則返回reject promise。

let allPromise = Promise。all([readFile(‘file1。txt’), readFile(‘file2。txt’)])allPromise。then(console。log, console。error)

我們所瞭解的Promise

總結

理解承諾的最好方法是使用它們。 以下是一些讓您入門的建議:

包裝一些標準的Node。js庫函式,將回調轉換為promises。 沒有使用node。promisify實用程式作弊!

使用async / await獲取函式並重寫它而不使用該語法糖。 這意味著您將返回一個promise並使用then方法。

使用promises遞迴寫一些東西(目錄樹將是一個好的開始)。