結合Promises/A+規範,深入解讀Promise(附原始碼)

香海流洋發表於2018-08-31

目錄


初步瞭解Promise

所謂Promise,字面上可以理解為“承諾”,表示一次操作的結果。和一般操作不同的是,Promise 物件不是實時的,而是在未來的某一時刻,會有返回,是一個能代表未來出現的結果的promise物件。 Promise允許你為非同步操作的成功和失敗分別繫結相應的處理方法(handlers)。

我們知道,JavaScript 在瀏覽器中是單執行緒呼叫的,但是有時我們又需要非同步操作,例如訪問檔案,或者是呼叫 ajax。這時候,我們就需要 Promise 了。

一個Promise有三種狀態:

  • pending:初始(等待)態,既不是成功,也不是失敗
  • fulfilled(resolved):意味著操作成功完成
  • rejected:意味著操作失敗

一個Promise的狀態只可能從“等待”轉到“完成”態或者“拒絕”態,不能逆向轉換,同時“完成”態和“拒絕”態不能相互轉換

Promise實現的類庫

為什麼需要這些類庫?

為什麼需要這些類庫呢?我想有些讀者不免會有此疑問。首先能想到的原因是有些執行環境並不支援 ES6 Promises 。

Promise相容如下

image

Promise擴充套件類庫

Promise擴充套件類庫除了實現了Promise中定義的規範之外,還增加了自己獨自定義的功能。

Promise擴充套件類庫數量非常的多,到底選擇哪個來使用完全看自己的喜好了,下面我們只提到其中兩個比較有名的

petkaantonov/bluebird

這個類庫除了相容 Promise 規範之外,還擴充套件了取消promise物件的執行,取得promise的執行進度,以及錯誤處理的擴充套件檢測等非常豐富的功能,此外它在實現上還在效能問題下了很大的功夫。

Bluebird 提供了很多方法來幫助我們把現有的庫函式由 Callback 的方式,轉化為 Promise 的方式,這叫做 Promise 化。最常見的,例如 Node 中預設提供的 fs 檔案操作控制程式碼,我們可以通過 Promise.promiseifyAll(fs) 的方式,把 fs 的呼叫,通過 Promise 返回。

let Promise = require("bluebird");
let fs = require("fs");
Promise.promisifyAll(fs);//這裡 fs 被改寫了,增加了 Async 字尾的方法
fs.readFileAsync('./welcome.txt', 'utf8').then((data) =>{
    console.log(data);
}
複製程式碼
kriskowal/q類庫

Q 實現了 Promises 和 Deferreds 等規範。 它自2009年開始開發,還提供了面向Node.js的檔案IO API Q-IO 等, 是一個在很多場景下都能用得到的類庫。

深度解析Promise + 原始碼實現

new Promise( function(resolve, reject) {...} );
複製程式碼

引數

在new Promise 時,需要傳遞一個executor 執行器,執行器會立刻執行。執行器中傳遞兩個引數, resolve reject

  • resolve:非同步操作執行成功後的回撥函式
  • reject:非同步操作執行失敗後的回撥函式

原始碼:new Promise原始碼實現,github檢視程式碼 點選檢視原始碼


Promise 原型方法

then
Promise.prototype.then(onFulfilled, onRejected)
複製程式碼

onFulfilled

當Promise成功態(fulfillment)時,該引數作為回撥函式被呼叫。該函式有一個引數,即成功時返回的最終結果(value)

onRejected

當Promise失敗態(rejection )時,該引數作為回撥函式被呼叫。該函式有一個引數,即拒絕的原因(reason)。

詳細解讀
  • 每個Promise都有then方法(可以說,then就是promise的核心),而且then必須返回一個promise
  • 同一個Promise的then可以呼叫多次,並且回撥的執行順序跟它們被定義時的順序一致
  • then方法接受兩個引數,第一個引數是成功時的回撥,在promise由“等待”態轉換到“完成”態時呼叫,另一個是失敗時的回撥,在promise由“等待”態轉換到“拒絕”態時呼叫
  • 如果then中返回的是一個結果的話,會把這個結果傳遞給下一個then
  • 如果then返回的是一個promise的話,會等待這個promise執行完,決定返回的那個 promise是成功還是失敗,如果成功走下一個then的成功
  • 每個then都返回一個新的promise,為什麼返回一個新的promise而不是this,因為 promise狀態確定好,就不能更改

then是否返回一個新的Promse,測試程式碼如下:

let promise1 = new Promise(function(resolve){
    resolve(1);
});
let thenPromise = promise1.then(function(value){
    console.log(value);
});
let catchPromise = thenPromise.catch(function(error){
    console.log(error);
});
console.log(promise1 !== thenPromise); // true
console.log(thenPromise !== catchPromise); //true
複製程式碼

如上程式碼,列印的都是true,這說明不管是then還是catch都返回的和新建立的promise是不同的物件


原始碼:上面關於then的詳細解讀已經描述的很詳細了,實現程式碼見github 點選檢視原始碼


catch
Promise.prototype.catch(onRejected)
複製程式碼

onRejected

當Promise 被rejected時,被呼叫的一個Function。 該函式擁有一個引數:reason 即rejection 失敗時返回的原因。

詳細解讀
  • 和then一樣返回結果為新的promise
  • 捕獲then沒有捕獲到的異常,一般放在最後,放在中間沒有意義
let p1 = Promise.resolve('成功')
p1.then(() => {
  console.log('p1 then')
  throw new Error();
}).catch(err => {
  console.log('catch ' + err);
})
//結果:
// p1 then
// catch Error
複製程式碼

注:即使你堅信不會出現異常,新增一個 catch() 總歸是更加謹慎的。如果你的假設最終被發現是錯誤的,它會讓你的生活更加美好。

方法

1. resolve

返回一個狀態為成功的Promise物件,並將給定資料結果傳遞給對應的處理方法

Promise.resolve(value)
複製程式碼
2. reject

返回一個狀態為失敗的Promise物件,並將給定的失敗資訊傳遞給對應的處理方法

Promise.resolve(reason)
複製程式碼
3. all

引數為陣列,返回陣列中所有的請求結果,並且返回資料的順序與請求的陣列順序一致。返回時間以最慢的為準(要麼全部成功,要麼全部失敗)

// 要麼全部成功
let p = [
  new Promise((resolve, reject) => resolve(1)),
  new Promise((resolve, reject) => resolve(2))
]
Promise.all(p).then(res => {
  console.log(res) // [1, 2]
})

// 要麼就失敗
let p1 = [
  new Promise((resolve, reject) => resolve(1)),
  new Promise((resolve, reject) => reject(2))
]
Promise.all(p1).then(res => {
  console.log(res)
},err => {
  console.log(err); // 2
})
複製程式碼

原始碼:Promise.all()原始碼實現,可直接下載測試上面例子 點選檢視原始碼


4. race

引數為陣列,返回的結果以最快的為準,且只有一個結果(誰最快就返回誰的結果)

// 要麼全部成功,返回執行完最快的結果
let p = [
  new Promise((resolve, reject) => resolve(1)),
  new Promise((resolve, reject) => resolve(2))
]
Promise.race(p).then(res => {
  console.log('success ' + res) // success 1或success 2 哪個執行完成的快返回哪個結果
})

// 要麼就失敗
let p1 = [
  new Promise((resolve, reject) => reject(1)),
  new Promise((resolve, reject) => resolve(2))
]
Promise.race(p1).then(res => {
  console.log('success' + res)
},err => {
  console.log('err'); // err
})
複製程式碼

原始碼:Promise.race()原始碼實現,可直接下載測試上面例子 原始碼實現見gitHub


以上詳細解讀,能幫助您更加了解Promise,下面一個經典的圖,幫助大家更好的理解promise

image

完整原始碼

為了方便大家測完整程式碼,詳見gitHub

總結

非同步程式設計,它幫助我們避免同步程式碼帶來的執行緒阻塞的問題,過多的回撥巢狀很容易會讓我們陷入“回撥地獄”中,使程式碼變成一團亂麻。使用Promise,可以有效的解決這個問題,並且還可以使我們的程式碼更加清晰、易讀。

參考閱讀:
作者:香香

將來的你,一定會感謝現在拼命努力的自己!

相關文章