j**ascript 應用的首選實現。
承諾是 j**ascript 開發中的乙個重要概念。 根據 Promise A+ 規範的定義,promise 表示非同步操作的最終結果。 與 promise 互動的方式是通過其 then(onfulfilled, onrejected) 方法,該方法註冊處理 promise 最終結果的方法。 promise 表示的非同步操作可能會成功,也可以失敗。 成功後,onfulfilled 方法接受乙個值作為最終結果; 失敗時,方法 onrejected 接受操作失敗的原因。
承諾可以處於以下三種狀態之一:待處理,這意味著與承諾對應的非同步操作仍在進行中; Fulfilled 表示非同步操作已成功完成; “拒絕”表示無法完成非同步操作。 promise 物件的 then 方法接受兩個引數,分別是滿足狀態和拒絕狀態。 滿足狀態方法的第乙個引數是 promise 的最終結果值; Rejected 方法的第乙個引數是 promise 被拒絕的原因。
Promise 是 ECMAScript 6 規範的一部分,並且已經有許多第三方庫提供與 promise 相關的實現。 Promise A+ 規範只是定義了 then 方法,該方法旨在實現不同實現之間的互操作性,重點關注 promise 的一般用例。 ecmascript 6 規範還定義了 promise 的其他方法,包括 promiseall、promise.race、promise.拒絕和承諾決議等。 可以在 nodejs 和瀏覽器環境中使用不同的 promise 實現。 本文中介紹的 Bluebird 是一種功能豐富且高效能的承諾實現。
根據操作環境的不同,可能會有不同的安裝方法。 在 nodejs 應用程式中,您可以直接使用 npm install bluebird 安裝它。 在瀏覽器應用程式中,您可以直接發布 jascript 檔案的版本,也可以使用 bower 安裝它。 本文中的示例基於 nodejs 環境,對應的版本為 LTS 89.版本 4,使用 ECMASCRIPT 6 的語法。 在 nodejs 環境中,你可以通過 const promise = require('bluebird') 開始使用 bluebird 提供的 promise 物件。
使用 Bluebird 作為 promise 實現時的第乙個問題是如何建立 promise 物件。 事實上,大多數時候,你不需要明確地建立承諾。 許多第三方庫已經返回包含 then 方法的 promise 物件或 thenable 物件,這些物件可以直接使用。
Bluebird 的乙個有用功能是將不使用 promise 的現有 API 包裝到返回 promise 的新 API 中。 NodeJS 的標準庫 API 和許多第三方庫 API 大多使用 **method 模式,即在執行非同步操作時,需要傳入乙個 **方法來接受操作的執行結果和可能的錯誤。 對於這樣的方法,Bluebird可以很容易地將它們轉換為承諾的形式。
例如,在 nodejs 中讀取檔案readfile 方法,常見的使用模式如清單 1 所示。 使用該方法的問題在於,它可能導致巢狀層過多,從而產生所謂的回撥地獄。
清單 1在 NodeJS 中 FSreadfile 方法的基本用法。
const fs = require('fs'),path = require('path');fs.readfile(path.join(__dirname, 'sample.txt'), 'utf-8', (err, data) => else })
藍鳥的承諾promisifyall 方法為物件屬性中的所有方法建立 promise 版本。 這些新建立的方法的名稱在現有方法的名稱後加上字尾“async”。 清單 1 中的實現可以更改為清單 2 中的承諾形式。 新的 readfileasync 方法對應於現有的 readfile 方法,但返回值是 promise 物件。 除了 readfile 之外,FS 中的其他方法也有相應的 async 版本,比如 writefileasync 和 fstatasync。
清單 2使用承諾promisifyall 來轉換方法。
const promise = require('bluebird'),fs = require('fs'),path = require('path');promise.promisifyall(fs);fs.readfileasync(path.join(__dirname, 'sample.txt'), 'utf-8').then(data => console.log(data)).catch(err => console.error(err));
如果不想自動將物件的所有方法轉換為 promise 的形式,則可以使用 promise承諾轉換單個方法,例如 Promisepromisify(require(“fs”).readfile)。對於 nodejs 格式的方法,可以使用 promiseFromCallback 將其轉換為承諾。 該方法的結果確定 promise 的狀態。
對於現有值,可以使用 promiseresolve 方法將其轉換為具有 Satisfied 狀態的 promise 物件。 再次,使用承諾reject 方法可以根據給定的拒絕原因建立狀態為 Rejected 的 promise 物件。 這兩種方法都是建立 promise 物件的快速方法。
如果這些方法都不能滿足建立 promise 的需要,則可以使用 promise 建構函式。 建構函式的引數是乙個方法,它接受兩個方法作為引數,分別用於將 promise 標記為滿足或拒絕。 在清單 3 中,建立的 promise 的狀態取決於生成的隨機數是否大於 05。
清單 3建立承諾
const promise = require('bluebird');const promise = new promise((resolve, reject) => else `)promise.then(console.log).catch(console.error);
在前面的示例中,我們看到了 promise 物件的基本用法。 其中,then 方法用於新增處理 promise 結果的方法。 您可以為“滿意”和“拒絕”狀態新增方法。 點差方法的工作方式與那時類似。 當 promise 的結果值為陣列時,可以使用 spread 方法將陣列的元素展平為 ** 方法的不同引數。 在清單 4 中,promise 的值是乙個包含 3 個值的陣列,分別對應於處理方法的 3 個引數中的每乙個。
清單 4傳播使用示例。
promise.resolve([1, 2, 3]).spread((v1, v2, v3) => console.log(v1 + v2 + v3));
catch 方法用於新增被拒絕的狀態。 有兩種方法可以使用 catch 方法。 第一種方式只新增**方法,捕獲所有錯誤情況; 第二種方法是使用錯誤物件型別或謂詞來篩選錯誤。 error 方法的工作方式與 catch 類似,但 error 僅處理實際的 error 物件。 j**ascript 中的 throw 語句可以丟擲任何值,而不一定是錯誤物件。 throw 丟擲的任何值都將被捕獲,但只有錯誤物件才會被 error 處理。 在清單 5 中,沒有呼叫錯誤新增的處理程式。
清單 5錯誤使用示例。
promise.reject('not an error').error(err => console.log('should not appear'));
最後,無論 promise 的最終狀態如何,都將呼叫該方法。 清單 6 中的 Catch 將只捕獲 TypeError,而 finally 中的邏輯將始終被呼叫。 清單 6抓住最後使用示例。
promise.reject(new typeerror('some error')).catch(typeerror, console.error).finally(()=> console.log('done'));
對於 promise 物件,您可以使用它提供的方法檢視其內部狀態。
iSpending 檢查承諾的狀態是否正在進行中。 isfulfilled 檢查是否滿足 promise 的狀態。 IsRejected 檢查 promise 的狀態是否為“已拒絕”。 iscancelled 檢查承諾是否已取消。 value 獲取承諾得到滿足的結果。 獲得承諾被拒絕的原因。 您可以使用 cancel 方法取消 promise,表示您不再對 promise 的結果感興趣。 取消 promise 時,不會呼叫其任何方法。 取消功能預設禁用,需要使用 promiseconfig 以啟用該功能。 需要注意的是,取消 promise 僅表示 promise 的方法不會被呼叫,並且不會自動取消正在進行的非同步操作,例如不會自動終止正在進行的 XHR 請求。 如果需要新增自定義取消邏輯,可以新增第三個引數 oncancel,在取消 promise 時註冊方法。
清單 7使用承諾的取消功能。
promise.config();promise.delay(1000, 'hello').then(console.log).cancel();
前面的例子是針對乙個承諾的。 在實踐中,通常需要與多個承諾進行互動。 例如,如果我們讀取單個檔案並返回乙個 promise 物件,則需要等待多個檔案成功後再執行其他操作。 promise.所有人都可以接受可迭代的引數並返回新的承諾。 只有當可迭代物件中包含的所有 promise 物件都得到滿足時,生成的 promise 才會得到滿足; 任何 promise 中的錯誤都將導致 promise 被拒絕的結果。 新 promise 的最終結果是乙個陣列,其中包含可迭代物件中 promise 的結果。 任何值都可以包含在可迭代物件中。 如果該值不是 Promise,則其值將直接顯示在結果中,無需等待 Promise 完成。 在清單 8 中,我們使用 promise全部等待 3 個讀取檔案操作完成。
清單 8 promise.所有用法示例。
promise.all([fs.readfileasync(path.join(__dirname, '1.txt'), 'utf-8'),fs.readfileasync(path.join(__dirname, '2.txt'), 'utf-8'),fs.readfileasync(path.join(__dirname, '3.txt'), 'utf-8')])then(results => console.log(results.join(', ')))catch(console.error);
在許多情況下,您需要將可迭代物件中的物件轉換為 Promise,並等待這些 Promise 完成。 對於此類方案,可以使用 promisemap。map 方法的第乙個引數是可迭代物件,第二個引數是轉換為 promise 的方法,第三個引數是可選的配置物件,可用於使用屬性併發來控制同時執行的 promise 數量。 在清單 9 中,我們使用 promiseMAP 將包含檔名稱的陣列轉換為讀取檔案內容的 Promise 物件,然後等待讀取操作完成。 功能與清單 8 相同,但實現更簡單。
清單 9 promise.map的使用示例。
promise.map(['1.txt', '2.txt', '3.txt'],name => fs.readfileasync(path.join(__dirname, name), 'utf-8')).then(results => console.log(results.join(', ')))catch(console.error);
promise.地圖系列的作用與承諾的作用相同地圖是相同的,只是地圖系列按可用順序遍歷每個元素。
當您有多個 promise 時,如果只需要等待其中一些 promise 完成,則可以使用 promisesome 並指定要完成的承諾數量。 在清單 10 中,您只需要等待 2 個檔案讀取操作完成。
清單 10 promise.一些使用的例子。
promise.some([fs.readfileasync(path.join(__dirname, '1.txt'), 'utf-8'),fs.readfileasync(path.join(__dirname, '2.txt'), 'utf-8'),fs.readfileasync(path.join(__dirname, '3.txt'), 'utf-8')],2).then(results => console.log(results.join(', ')))catch(console.error);
promise.Any 等同於使用 promisesome 並將數量設定為 1,但承諾any 的結果不是長度為 1 的陣列,而是特定的單個值。 promise.種族的運作方式與任何種族類似,但種族的結果可能是被拒絕的承諾。 因此,推薦的做法是使用任何。
promise.篩選器可以等待多個承諾完成並篩選結果。 它實際上的工作方式與承諾相同map,然後使用陣列的 filter 方法進行篩選。 在清單 11 中,promise篩選器用於篩選出內容長度為 1 或更小的檔案。
清單 11 promise.過濾器的使用示例。
promise.filter([fs.readfileasync(path.join(__dirname, '1.txt'), 'utf-8'),fs.readfileasync(path.join(__dirname, '2.txt'), 'utf-8'),fs.readfileasync(path.join(__dirname, '3.txt'), 'utf-8')],value => value.length > 1).then(results => console.log(results.join(', ')))catch(console.error);
promise.每個依次處理可迭代物件中的元素。 處理方法還可以返回乙個 promise 物件來表示非同步處理。 只有在處理完前乙個元素後,才會處理下乙個元素。
promise.reduce 將多個 promise 的結果減少為單個值。 它的工作方式類似於陣列的reduce方法,但可以處理promise物件。 在清單 12 中,第乙個引數是乙個掛起檔名的陣列; 第二個引數是累加操作的方法,total表示當前累加值,用於將當前讀取的檔案長度相加; 第三個引數是累積的初始值。 從**中可以看出,累積操作也可以返回乙個 promise 物件。 promise.減少等待 promise 完成的等待時間。
清單 12 promise.reduce的使用示例。
promise.reduce(['1.txt', '2.txt', '3.txt'],(total, name) => ,0).then(result => console.log(`total size: $catch(console.error);
promise.all 用於處理動態數量的 promise、promise聯接用於處理固定數量的不相關承諾。
promise.method 用於封裝方法,以便它返回 promise。 promise.Try 用於封裝可能導致問題的方法呼叫,並根據呼叫結果確定 promise 的狀態。
如果使用Promise中需要釋放的資源,例如資料庫連線,則不容易確保這些資源被釋放。 一般做法是將資源釋放邏輯新增到 finally。 但是,當承諾相互巢狀時,很容易獲得未釋放的資源。 Bluebird 提供了一種更強大的方式來管理資源,使用處置程式和承諾using 方法。
資源釋放器由乙個 disposer 物件表示,該物件是通過 promise 的 disposer 方法建立的。 create-time 引數是用於釋放資源的方法。 該方法的第乙個引數是資源物件,第二個引數是 promise使用生成的 promise 物件。 處置器物件可以傳遞給 promise用於確保其資源釋放邏輯得到執行。
在清單 13 中,connection 表示資料庫連線,db 表示資料庫。 DB 的 Connect 方法建立乙個返回 Promise 物件的資料庫連線。 Connection 的查詢方法表示資料庫查詢的執行,返回的結果也是乙個 Promise 物件。 Connection 的 Close 方法關閉資料庫連線。 使用時,在 connect 方法返回的 promise 物件之上使用 disposer 建立 disposer 物件,對應的資源釋放邏輯是呼叫 close 方法。 然後你就可以傳遞承諾了using 使用連線物件。
清單 13承諾。
const promise = require('bluebird');class connection close() class db }const disposer = new db().connect().disposer(connection => connection.close())promise.using(disposer, connection => connection.query())then(console.log).catch(console.error);
promise.延遲返回的 promise 在指定時間後轉換為滿足狀態,結果為指定值。 promise.超時用於向現有 promise 新增超時。 如果現有 promise 未在指定的超時期限內完成,則生成的 promise 將生成 timeouterror 錯誤。
在清單 14 中,第乙個延遲方法在 1 秒後輸出 hello。 第二種方法引發 TimeOutError 錯誤,因為 1 秒的延遲超過了 500 毫秒的超時。
清單 14 promise.延遲和承諾超時的使用示例。
promise.delay(1000, 'hello').then(console.log);promise.delay(1000, 'hello').timeout(500, 'timed out').then(console.log).catch(console.error);
該承諾還包括一些實用的方法。 Tap 和 tapcatch 分別用於檢視 promise 中的結果和發生的錯誤。 這兩種方法的處理不影響承諾的結果,適用於日誌記錄。 call 用於呼叫 Promise Result 物件中的方法。 get 用於獲取 Promise 結果物件中屬性的值。 return 用於更改 promise 的結果。 throw 用於丟擲錯誤。 CatchReturn 用於在捕獲錯誤後更改 promise 的值。 CatchThrow 用於捕獲錯誤,然後拋出新錯誤。
*清單 15 中給出了這些實用方法的使用示例。 第乙個語句使用 tap 輸出 promise 的結果。 第二條語句使用 call 呼叫 sayhi 方法,並將該方法的返回值作為 promise 的結果。 第三個語句使用 get 來獲取作為 promise 結果的屬性 b 的值。 第四個語句使用 return 來更改 promise 的結果。 第五條語句使用 throw 丟擲錯誤,使用 catch 來處理錯誤。 第六條語句使用 catchreturn 來處理錯誤並更改 promise 的值。
清單 15使用實用方法的例子。
promise.resolve(1).tap(console.log).then(v => v + 1).then(console.log);promise.resolve().call('sayhi').then(console.log);promise.resolve().get('b').then(console.log);promise.resolve(1).return(2).then(console.log);promise.resolve(1).throw(new typeerror('type error')).catch(console.error);promise.reject(new typeerror('type error')).catchreturn('default value').then(console.log);
雖然可以使用 promise 捕獲來捕獲和處理錯誤,但在許多情況下,由於程式本身或第三方庫的問題,仍然有可能未收到錯誤。 未捕獲的錯誤可能導致 nodejs 程序意外退出。 Bluebird 提供全域性和本地錯誤處理機制。 Bluebird 觸發與拒絕 promise 相關的全域性事件,分別是 unhandledrefusal 和 rejectionhandled。 您可以新增全域性事件處理程式來處理這兩種型別的事件。 對於每個 promise,您可以使用 onpossiblyunhandledrejection 方法為未處理的拒絕錯誤新增預設處理方法。 如果您不關心未處理的錯誤,則可以使用 suppressUnhandledRejections 忽略錯誤。
在清單 16 中,promise 的拒絕錯誤不被處理,而是由全域性處理器處理。
清單 16錯誤處理示例。
promise.reject(new typeerror('error')).then(console.log);process.on('unhandledrejection', (reason, promise) => console.error(`unhandled $`
Promise 是與 j**ascript 應用程式開發中的非同步操作相關的重要抽象。 作為乙個流行的 promise 庫,Bluebird 提供了許多與 promise 相關的有用功能。 本文詳細介紹了 Bluebird 的重要功能,包括如何建立 promise 物件、使用 promise、使用集合、promise 實用程式和錯誤處理。