關於 Promise
原理解析的優秀文章,在掘金上已經有很是多了。可是筆者老是處在 看了就會,一寫就廢 的狀態,這是筆者寫這篇文章的目的,爲了理一下 Promise
的編寫思路,從零開始手寫一波代碼,同時也方便本身往後回顧。javascript
Promise
是 JavaScript
異步編程的一種流行解決方案,它的出現是爲了解決 回調地獄 的問題,讓使用者能夠經過鏈式的寫法去編寫寫異步代碼,具體的用法筆者就不介紹了,你們能夠參考阮一峯老師的 ES6 Promise教程。java
什麼是觀察者模式:git
觀察者模式定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一個目標對象,當這個目標對象的狀態發生變化時,會通知全部觀察者對象,使它們可以自動更新。
Promise
是基於 觀察者的設計模式 實現的,then
函數要執行的函數會被塞入觀察者數組中,當 Promise
狀態變化的時候,就去執行觀察組數組中的全部函數。es6
實現 Promise
涉及到了 JavaScript
中的事件循環機制 EventLoop
、以及宏任務和微任務的概念。github
事件循環機制的流程圖以下:面試
你們能夠看一下這段代碼:typescript
console.log(1); setTimeout(() => { console.log(2); },0); let a = new Promise((resolve) => { console.log(3); resolve(); }).then(() => { console.log(4); }).then(() => { console.log(5); }); console.log(6);
若是不能一會兒說出輸出結果,建議你們能夠先查閱一下 事件循環 的相關資料,在掘金中有不少優秀的文章。shell
Promises/A+ 是一個社區規範,若是你想寫出一個規範的 Promise
,咱們就須要遵循這個標準。以後咱們也會根據規範來完善咱們本身編寫的 Promise
。npm
在動手寫 Promise
以前,咱們先過一下幾個重要的知識點。編程
// 建立 Promise 對象 x1 // 並在 executor 函數中執行業務邏輯 function executor(resolve, reject){ // 業務邏輯處理成功結果 const value = ...; resolve(value); // 失敗結果 // const reason = ...; // reject(reason); } let x1 = new Promise(executor);
首先 Promise
是一個類,它接收一個執行函數 executor
,它接收兩個參數:resolve
和 reject
,這兩個參數是 Promise
內部定義的兩個函數,用來改變狀態並執行對應回調函數。
由於Promise
自己是不知道執行結果失敗或者成功,它只是給異步操做提供了一個容器,實際上的控制權在使用者的手上,使用者能夠調用上面兩個參數告訴Promise
結果是否成功,同時將業務邏輯處理結果(value/reason
)做爲參數傳給resolve
和reject
兩個函數,執行回調。
Promise
有三個狀態:
pending
:等待中resolved
:已成功rejected
:已失敗在 Promise
的狀態改變只有兩種可能:從 pending
變爲 resolved
或者從 pending
變爲 rejected
,以下圖(引自 Promise 迷你書):
並且須要注意的是一旦狀態改變,狀態不會再變了,接下來就一直是這個結果。也就是說當咱們在 executor
函數中調用了 resolve
以後,以後調用 reject
就沒有效果了,反之亦然。
// 並在 executor 函數中執行業務邏輯 function executor(resolve, reject){ resolve(100); // 以後調用 resolve,reject 都是無效的, // 由於狀態已經變爲 resolved,不會再改變了 reject(100); } let x1 = new Promise(executor);
每個 promise
都一個 then
方法,這個是當 promise
返回結果以後,須要執行的回調函數,他有兩個可選參數:
onFulfilled
:成功的回調;onRejected
:失敗的回調;以下圖(引自 Promise 迷你書):
// ... let x1 = new Promise(executor); // x1 延遲綁定回調函數 onResolve function onResolved(value){ console.log(value); } // x1 延遲綁定回調函數 onRejected function onRejected(reason){ console.log(reason); } x1.then(onResolved, onRejected);
在這裏咱們簡單過一下手寫一個 Promise
的大體流程:
new Promise
時,須要傳遞一個 executor
執行器函數,在構造函數中,執行器函數馬上執行 executor
執行函數接受兩個參數,分別是 resolve
和 reject
Promise
只能從 pending
到 rejected
, 或者從 pending
到 fulfilled
Promise
的狀態一旦確認,狀態就凝固了,不在改變Promise
都有 then
方法,then
接收兩個參數,分別是 Promise
成功的回調 onFulfilled
,和失敗的回調 onRejected
then
時,Promise
已經成功,則執行 onFulfilled
,並將 Promise
的值做爲參數傳遞進去;若是 Promise
已經失敗,那麼執行 onRejected
,並將 Promise
失敗的緣由做爲參數傳遞進去;若是 Promise
的狀態是 pending
,須要將 onFulfilled
和 onRejected
函數存放起來,等待狀態肯定後,再依次將對應的函數執行(觀察者模式)then
的參數 onFulfilled
和 onRejected
能夠不傳,Promise
能夠進行值穿透。Promise
能夠 then
屢次,Promise
的 then
方法返回一個新的 Promise
。then
返回的是一個正常值,那麼就會把這個結果(value
)做爲參數,傳遞給下一個 then
的成功的回調(onFulfilled
)then
中拋出了異常,那麼就會把這個異常(reason
)做爲參數,傳遞給下一個 then
的失敗的回調(onRejected
)then
返回的是一個 promise
或者其餘 thenable
對象,那麼須要等這個 promise
執行完撐,promise
若是成功,就走下一個 then
的成功回調;若是失敗,就走下一個 then
的失敗回調。上面是大體的實現流程,若是迷迷糊糊不要緊,只要大體有一個印象便可,後續咱們會一一講到。
那接下來咱們就開始實現一個最簡單的例子開始講解。
咱們先寫一個簡單版,這版暫不支持狀態、鏈式調用,而且只支持調用一個
then
方法。
let p1 = new MyPromise((resolve, reject) => { setTimeout(() => { resolved('成功了'); }, 1000); }) p1.then((data) => { console.log(data); }, (err) => { console.log(err); })
例子很簡單,就是 1s
以後返回 成功了
,並在 then
中輸出。
咱們定義一個 MyPromise
類,接着咱們在其中編寫代碼,具體代碼以下:
class MyPromise { // ts 接口定義 ... constructor (executor: executor) { // 用於保存 resolve 的值 this.value = null; // 用於保存 reject 的值 this.reason = null; // 用於保存 then 的成功回調 this.onFulfilled = null; // 用於保存 then 的失敗回調 this.onRejected = null; // executor 的 resolve 參數 // 用於改變狀態 並執行 then 中的成功回調 let resolve = value => { this.value = value; this.onFulfilled && this.onFulfilled(this.value); } // executor 的 reject 參數 // 用於改變狀態 並執行 then 中的失敗回調 let reject = reason => { this.reason = reason; this.onRejected && this.onRejected(this.reason); } // 執行 executor 函數 // 將咱們上面定義的兩個函數做爲參數 傳入 // 有可能在 執行 executor 函數的時候會出錯,因此須要 try catch 一下 try { executor(resolve, reject); } catch(err) { reject(err); } } // 定義 then 函數 // 而且將 then 中的參數複製給 this.onFulfilled 和 this.onRejected private then(onFulfilled, onRejected) { this.onFulfilled = onFulfilled; this.onRejected = onRejected; } }
好了,咱們的初版就完成了,是否是很簡單。
不過這裏須要注意的是,resolve
函數的執行時機須要在then
方法將回調函數註冊了以後,在resolve
以後在去往賦值回調函數,其實已經完了,沒有任何意義。上面的例子沒有問題,是由於
resolve(成功了)
是包在setTimeout
中的,他會在下一個宏任務執行,這時回調函數已經註冊了。你們能夠試試把
resolve(成功了)
從setTimeout
中拿出來,這個時候就會出現問題了。
這一版實現很簡單,還存在幾個問題:
未引入狀態的概念,如今狀態能夠隨意變,不符合 Promise
狀態只能從等待態變化的規則。
正常狀況下咱們能夠對 Promise
進行鏈式調用:
let p1 = new MyPromise((resolve, reject) => { setTimeout(() => { resolved('成功了'); }, 1000); }) p1.then(onResolved1, onRejected1).then(onResolved2, onRejected2)
在這個例子中,onResolved2
會覆蓋 onResolved1
,onRejected2
會覆蓋 onRejected1
。
let p1 = new MyPromise((resolve, reject) => { setTimeout(() => { resolved('成功了'); }, 1000); }) // 註冊多個回調函數 p1.then(onResolved1, onRejected1); p1.then(onResolved2, onRejected2);
接下來咱們更進一步,把這些問題給解決掉。
這一版咱們把狀態的概念引入,同時實現鏈式調用的功能。
上面咱們說到 Promise
有三個狀態:pending
、resovled
、rejected
,只能從 pending
轉爲 resovled
或者 rejected
,並且當狀態改變以後,狀態就不能再改變了。
status
:用於記錄當前 Promise
的狀態PENDING
、RESOLVED
、REJECTED
。then
的成功回調定義爲一個數組:this.resolvedQueues
與 this.rejectedQueues
,咱們能夠把 then
中的回調函數都塞入對應的數組中,這樣就能解決咱們上面提到的第三個問題。class MyPromise { private static PENDING = 'pending'; private static RESOLVED = 'resolved'; private static REJECTED = 'rejected'; constructor (executor: executor) { this.status = MyPromise.PENDING; // ... // 用於保存 then 的成功回調數組 this.resolvedQueues = []; // 用於保存 then 的失敗回調數組 this.rejectedQueues = []; let resolve = value => { // 當狀態是 pending 是,將 promise 的狀態改成成功態 // 同時遍歷執行 成功回調數組中的函數,將 value 傳入 if (this.status == MyPromise.PENDING) { this.value = value; this.status = MyPromise.RESOLVED; this.resolvedQueues.forEach(cb => cb(this.value)) } } let reject = reason => { // 當狀態是 pending 是,將 promise 的狀態改成失敗態 // 同時遍歷執行 失敗回調數組中的函數,將 reason 傳入 if (this.status == MyPromise.PENDING) { this.reason = reason; this.status = MyPromise.REJECTED; this.rejectedQueues.forEach(cb => cb(this.reason)) } } try { executor(resolve, reject); } catch(err) { reject(err); } } }
接着咱們來完善 then
中的方法,以前咱們是直接將 then
的兩個參數 onFulfilled
和 onRejected
,直接賦值給了 Promise
的用於保存成功、失敗函數回調的實例屬性。
如今咱們須要將這兩個屬性塞入到兩個數組中去:resolvedQueues
和 rejectedQueues
。
class MyPromise { // ... private then(onFulfilled, onRejected) { // 首先判斷兩個參數是否爲函數類型,由於這兩個參數是可選參數 // 當參數不是函數類型時,須要建立一個函數賦值給對應的參數 // 這也就實現了 透傳 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason} // 當狀態是等待態的時候,須要將兩個參數塞入到對應的回調數組中 // 當狀態改變以後,在執行回調函數中的函數 if (this.status === MyPromise.PENDING) { this.resolvedQueues.push(onFulfilled) this.rejectedQueues.push(onRejected) } // 狀態是成功態,直接就調用 onFulfilled 函數 if (this.status === MyPromise.RESOLVED) { onFulfilled(this.value) } // 狀態是成功態,直接就調用 onRejected 函數 if (this.status === MyPromise.REJECTED) { onRejected(this.reason) } } }
this.status
會是 pending
狀態,什麼狀況下會是 resolved
狀態這個其實也和事件循環機制有關,以下代碼:
// this.status 爲 pending 狀態 new MyPromise((resolve, reject) => { setTimeout(() => { resolve(1) }, 0) }).then(value => { console.log(value) }) // this.status 爲 resolved 狀態 new MyPromise((resolve, reject) => { resolve(1) }).then(value => { console.log(value) })
以下面代碼,當 then
中沒有傳任何參數的時候,Promise
會使用內部默認的定義的方法,將結果傳遞給下一個 then
。
let p1 = new MyPromise((resolve, reject) => { setTimeout(() => { resolved('成功了'); }, 1000); }) p1.then().then((res) => { console.log(res); })
由於咱們如今還沒支持鏈式調用,這段代碼運行會出問題。
支持鏈式調用,其實很簡單,咱們只須要給 then
函數最後返回 this
就行,這樣就支持了鏈式調用:
class MyPromise { // ... private then(onFulfilled, onRejected) { // ... return this; } }
每次調用 then
以後,咱們都返回當前的這個 Promise
對象,由於 Promise
對象上是存在 then
方法的,這個時候咱們就簡單的實現了 Promise
的簡單調用。
這個時候運行上面 透傳 的測試代碼了。
可是上面的代碼仍是存在相應的問題的,看下面代碼:
const p1 = new MyPromise((resolved, rejected) => { resolved('resolved'); }); p1.then((res) => { console.log(res); return 'then1'; }) .then((res) => { console.log(res); return 'then2'; }) .then((res) => { console.log(res); return 'then3'; }) // 預測輸出:resolved -> then1 -> then2 // 實際輸出:resolved -> resolved -> resolved
輸出與咱們的預期有誤差,由於咱們 then
中返回的 this
表明了 p1
,在 new MyPromise
以後,其實狀態已經從 pending
態變爲了 resolved
態,以後不會再變了,因此在 MyPromise
中的 this.value
值就一直是 resolved
。
這個時候咱們就得看看關於 then
返回值的相關知識點了。
實際上 then
都會返回了一個新的 Promise
對象。
先看下面這段代碼:
// 新建立一個 promise const aPromise = new Promise(function (resolve) { resolve(100); }); // then 返回的 promise var thenPromise = aPromise.then(function (value) { console.log(value); }); console.log(aPromise !== thenPromise); // => true
從上面的代碼中咱們能夠得出 then
方法返回的 Promise
已經再也不是最初的 Promise
了,以下圖(引自 Promise 迷你書):
promise
的鏈式調用跟jQuery
的鏈式調用是有區別的,jQuery
鏈式調用返回的對象仍是最初那個jQuery
對象;Promise
更相似於數組中一些方法,如slice
,每次進行操做以後,都會返回一個新的值。
class MyPromise { // ... private then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason} // then 方法返回一個新的 promise const promise2 = new MyPromise((resolve, reject) => { // 成功狀態,直接 resolve if (this.status === MyPromise.RESOLVED) { // 將 onFulfilled 函數的返回值,resolve 出去 let x = onFulfilled(this.value); resolve(x); } // 失敗狀態,直接 reject if (this.status === MyPromise.REJECTED) { // 將 onRejected 函數的返回值,reject 出去 let x = onRejected(this.reason) reject && reject(x); } // 等待狀態,將 onFulfilled,onRejected 塞入數組中,等待回調執行 if (this.status === MyPromise.PENDING) { this.resolvedQueues.push((value) => { let x = onFulfilled(value); resolve(x); }) this.rejectedQueues.push((reason) => { let x = onRejected(reason); reject && reject(x); }) } }); return promise2; } } // 輸出結果 resolved -> then1 -> then2
到這裏咱們就完成了簡單的鏈式調用,可是隻能支持同步的鏈式調用,若是咱們須要在 then
方法中再去進行其餘異步操做的話,上面的代碼就 GG 了。
以下代碼:
const p1 = new MyPromise((resolved, rejected) => { resolved('我 resolved 了'); }); p1.then((res) => { console.log(res); return new MyPromise((resolved, rejected) => { setTimeout(() => { resolved('then1'); }, 1000) }); }) .then((res) => { console.log(res); return new MyPromise((resolved, rejected) => { setTimeout(() => { resolved('then2'); }, 1000) }); }) .then((res) => { console.log(res); return 'then3'; })
上面的代碼會直接將 Promise
對象直接看成參數傳給下一個 then
函數,而咱們實際上是想要將這個 Promise
的處理結果傳遞下去。
這一版咱們來實現
promise
的異步鏈式調用。
先看一下 then
中 onFulfilled
和 onRejected
返回的值:
// 成功的函數返回 let x = onFulfilled(this.value); // 失敗的函數返回 let x = onRejected(this.reason);
從上面的的問題中能夠看出,x
能夠是一個 普通值,也能夠是一個 Promise
對象,普通值的傳遞咱們在 第二版 已經解決了,如今須要解決的是當 x
返回一個 Promise
對象的時候該怎麼處理。
其實也很簡單,當 x
是一個 Promise
對象的時候,咱們須要進行等待,直到返回的 Promise
狀態變化的時候,再去執行以後的 then
函數,代碼以下:
class MyPromise { // ... private then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason} // then 方法返回一個新的 promise const promise2 = new MyPromise((resolve, reject) => { // 成功狀態,直接 resolve if (this.status === MyPromise.RESOLVED) { // 將 onFulfilled 函數的返回值,resolve 出去 let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } // 失敗狀態,直接 reject if (this.status === MyPromise.REJECTED) { // 將 onRejected 函數的返回值,reject 出去 let x = onRejected(this.reason) resolvePromise(promise2, x, resolve, reject); } // 等待狀態,將 onFulfilled,onRejected 塞入數組中,等待回調執行 if (this.status === MyPromise.PENDING) { this.resolvedQueues.push(() => { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); }) this.rejectedQueues.push(() => { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); }) } }); return promise2; } }
咱們新寫一個函數 resolvePromise
,這個函數是用來處理異步鏈式調用的核心方法,他會去判斷 x
返回值是否是 Promise
對象,若是是的話,就直到 Promise
返回成功以後在再改變狀態,若是是普通值的話,就直接將這個值 resovle
出去:
const resolvePromise = (promise2, x, resolve, reject) => { if (x instanceof MyPromise) { const then = x.then; if (x.status == MyPromise.PENDING) { then.call(x, y => { resolvePromise(promise2, y, resolve, reject); }, err => { reject(err); }) } else { x.then(resolve, reject); } } else { resolve(x); } }
resolvePromise
接受四個參數:
promise2
是 then
中返回的 promise
;x
是 then
的兩個參數 onFulfilled
或者 onRejected
的返回值,類型不肯定,有多是普通值,有多是 thenable
對象;resolve
和 reject
是 promise2
的。當 x
是 Promise
的時,而且他的狀態是 Pending
狀態,若是 x
執行成功,那麼就去遞歸調用 resolvePromise
這個函數,將 x
執行結果做爲 resolvePromise
第二個參數傳入;
若是執行失敗,則直接調用 promise2
的 reject
方法。
到這裏咱們基本上一個完整的 promise
,接下來咱們須要根據 Promises/A+ 來規範一下咱們的 Promise
。
前幾版的代碼筆者基本上是按照規範來的,這裏主要講幾個沒有符合規範的點。
then
中onFulfilled
和onRejected
須要異步執行,即放到異步任務中去執行(規範 2.2.4)
咱們須要將 then
中的函數經過 setTimeout
包裹起來,放到一個宏任務中去,這裏涉及了 js
的 EventLoop
,你們能夠去看看相應的文章,以下:
class MyPromise { // ... private then(onFulfilled, onRejected) { // ... // then 方法返回一個新的 promise const promise2 = new MyPromise((resolve, reject) => { // 成功狀態,直接 resolve if (this.status === MyPromise.RESOLVED) { // 將 onFulfilled 函數的返回值,resolve 出去 setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch(err) { reject(err); } }) } // 失敗狀態,直接 reject if (this.status === MyPromise.REJECTED) { // 將 onRejected 函數的返回值,reject 出去 setTimeout(() => { try { let x = onRejected(this.reason) resolvePromise(promise2, x, resolve, reject); } catch(err) { reject(err); } }) } // 等待狀態,將 onFulfilled,onRejected 塞入數組中,等待回調執行 if (this.status === MyPromise.PENDING) { this.resolvedQueues.push(() => { setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch(err) { reject(err); } }) }) this.rejectedQueues.push(() => { setTimeout(() => { try { let x = onRejected(this.reason) resolvePromise(promise2, x, resolve, reject); } catch(err) { reject(err); } }) }) } }); return promise2; } }
但這樣仍是有一個問題,咱們知道其實 Promise.then
是屬於微任務的,如今當使用 setTimeout
包裹以後,就至關於會變成一個宏任務,能夠看下面這一個例子:
var p1 = new MyPromise((resolved, rejected) => { resolved('resolved'); }) setTimeout(() => { console.log('---setTimeout---'); }, 0); p1.then(res => { console.log('---then---'); }) // 正常 Promise:then -> setTimeout // 咱們的 Promise:setTimeout -> then
輸出順序不同,緣由是由於如今的 Promise
是經過 setTimeout
宏任務包裹的。
咱們能夠改進一下,使用微任務來包裹 onFulfilled
、onRejected
,經常使用的微任務有 process.nextTick
、MutationObserver
、postMessage
等,咱們這個使用 postMessage
改寫一下:
// ... if (this.status === MyPromise.RESOLVED) { // 將 onFulfilled 函數的返回值,resolve 出去 // 註冊一個 message 事件 window.addEventListener('message', event => { const { type, data } = event.data; if (type === '__promise') { try { let x = onFulfilled(that.value); resolvePromise(promise2, x, resolve, reject); } catch(err) { reject(err); } } }); // 立馬執行 window.postMessage({ type: '__promise', }, "http://localhost:3001"); } // ...
實現方法很簡單,咱們監聽window
的 message
事件,並在以後立馬觸發一個 postMessage
事件,這個時候其實 then
中的回調函數已經在微任務隊列中了,咱們從新運行一下例子,能夠看到輸出的順序變爲了 then -> setTimeout
。
固然
Promise
內部實現確定沒有這麼簡單,筆者在這裏只是提供一種思路,你們有興趣能夠去研究一波。
重複引用,當x
和promise2
是同樣的,那就須要報一個錯誤,重複應用。(規範 2.3.1)
由於本身等待本身完成是永遠都不會有結果的。
const p1 = new MyPromise((resolved, rejected) => { resolved('我 resolved 了'); }); const p2 = p1.then((res) => { return p2; });
大體分爲一下這麼幾條:
x
是一個 Promise
,那麼就等待 x
改變狀態以後,纔算完成或者失敗(這個也屬於 2.3.3
,由於 Promise
其實也是一個 thenable
對象)x
是一個對象 或者 函數的時候,即 thenable
對象,那就那 x.then
做爲 then
x
不是一個對象,或者函數的時候,直接將 x
做爲參數 resolve
返回。咱們主要看一下 2.3.3
就行,由於 Prmise
也屬於 thenable
對象,那什麼是 thenable
對象呢?
簡單來講就是具備 then方法的對象/函數,全部的
Promise
對象都是thenable
對象,但並不是全部的thenable
對象並不是是Promise
對象。以下:let thenable = { then: function(resolve, reject) { resolve(100); } }
根據 x
的類型進行處理:
x
不是 thenable
對象,直接調用 Promise2
的 resolve
,將 x
做爲成功的結果;x
是 thenable
對象,會調用 x
的 then
方法,成功後再去調用 resolvePromise
函數,並將執行結果 y
做爲新的 x
傳入 resolvePromise
,直到這個 x
值再也不是一個 thenable
對象爲止;若是失敗則直接調用 promise2
的 reject
。if (x != null && (typeof x === 'object' || typeof x === 'function')) { if (typeof then === 'function') { then.call(x, (y) => { resolvePromise(promise2, y, resolve, reject); }, (err) => { reject(err); }) } } else { resolve(x); }
規範(Promise/A+ 2.3.3.3.3
)規定若是同時調用resolvePromise
和rejectPromise
,或者對同一參數進行了屢次調用,則第一個調用優先,而全部其餘調用均被忽略,確保只執行一次改變狀態。
咱們在外面定義了一個 called
佔位符,爲了得到 then
函數有沒有執行過相應的改變狀態的函數,執行過了以後,就再也不去執行了,主要就是爲了知足規範。
若是 x
是 Promise
對象的話,其實當執行了resolve
函數 以後,就不會再執行 reject
函數了,是直接在當前這個 Promise
對象就結束掉了。
當 x
是普通的 thenable
函數的時候,他就有可能同時執行 resolve
和 reject
函數,便可以同時執行 promise2
的 resolve
函數 和 reject
函數,可是其實 promise2
在狀態改變了以後,也不會再改變相應的值了。其實也沒有什麼問題,以下代碼:
// thenable 對像 { then: function(resolve, reject) { setTimeout(() => { resolve('我是thenable對像的 resolve'); reject('我是thenable對像的 reject') }) } }
完整的 resolvePromise
函數以下:
const resolvePromise = (promise2, x, resolve, reject) => { if(x === promise2){ return reject(new TypeError('Chaining cycle detected for promise')); } let called; if (x != null && (typeof x === 'object' || typeof x === 'function')) { try { let then = x.then; if (typeof then === 'function') { then.call(x, y => { if(called)return; called = true; resolvePromise(promise2, y, resolve, reject); }, err => { if(called)return; called = true; reject(err); }) } else { resolve(x); } } catch (e) { if(called)return; called = true; reject(e); } } else { resolve(x); } }
到這裏就大功告成了,開不開心,興不興奮!
最後咱們能夠經過測試腳本跑一下咱們的 MyPromise
是否符合規範。
有專門的測試腳本(promises-aplus-tests
)能夠幫助咱們測試所編寫的代碼是否符合 Promise/A+
的規範。
可是貌似只能測試js
文件,因此筆者就將ts
文件轉化爲了js
文件,進行測試
在代碼裏面加上:
// 執行測試用例須要用到的代碼 MyPromise.deferred = function() { let defer = {}; defer.promise = new MyPromise((resolve, reject) => { defer.resolve = resolve; defer.reject = reject; }); return defer; }
須要提早安裝一下測試插件:
# 安裝測試腳本 npm i -g promises-aplus-tests # 開始測試 promises-aplus-tests MyPromise.js
結果以下:
完美經過,接下去咱們就能夠看看 Promise
更多方法的實現了。
實現上面的 Promise
以後,其實編寫其實例和靜態方法,相對來講就簡單了不少。
其實這個方法就是 then
方法的語法糖,只須要給 then
傳遞 onRejected
參數就 ok
了。
private catch(onRejected) { return this.then(null, onRejected); }
const p1 = new MyPromise((resolved, rejected) => { resolved('resolved'); }) p1.then((res) => { return new MyPromise((resolved, rejected) => { setTimeout(() => { rejected('錯誤了'); }, 1000) }); }) .then((res) => { return new MyPromise((resolved, rejected) => { setTimeout(() => { resolved('then2'); }, 1000) }); }) .then((res) => { return 'then3'; }).catch(error => { console.log('----error', error); }) // 1s 以後輸出:----error 錯誤了
finally()
方法用於指定無論 Promise
對象最後狀態如何,都會執行的操做。
private finally (fn) { return this.then(fn, fn); }
const p1 = new MyPromise((resolved, rejected) => { resolved('resolved'); }) p1.then((res) => { return new MyPromise((resolved, rejected) => { setTimeout(() => { rejected('錯誤了'); }, 1000) }); }) .then((res) => { return new MyPromise((resolved, rejected) => { setTimeout(() => { resolved('then2'); }, 1000) }); }) .then((res) => { return 'then3'; }).catch(error => { console.log('---error', error); return `catch-${error}` }).finally(res => { console.log('---finally---', res); }) // 輸出結果:---error 錯誤了" -> ""---finally--- catch-錯誤了
有時須要將現有對象轉爲 Promise
對象,Promise.resolve()
方法就起到這個做用。
static resolve = (val) => { return new MyPromise((resolve,reject) => { resolve(val); }); }
MyPromise.resolve({name: 'darrell', sex: 'boy' }).then((res) => { console.log(res); }).catch((error) => { console.log(error); }); // 輸出結果:{name: "darrell", sex: "boy"}
Promise.reject(reason)
方法也會返回一個新的 Promise
實例,該實例的狀態爲 rejected
。
static reject = (val) => { return new MyPromise((resolve,reject) => { reject(val) }); }
MyPromise.reject("出錯了").then((res) => { console.log(res); }).catch((error) => { console.log(error); }); // 輸出結果:出錯了
Promise.all()
方法用於將多個 Promise
實例,包裝成一個新的 Promise
實例,
const p = Promise.all([p1, p2, p3]);
p1
、p2
、p3
的狀態都變成 fulfilled
,p
的狀態纔會變成 fulfilled
;p1
、p2
、p3
之中有一個被 rejected
,p
的狀態就變成 rejected
,此時第一個被 reject
的實例的返回值,會傳遞給p
的回調函數。static all = (promises: MyPromise[]) => { return new MyPromise((resolve, reject) => { let result: MyPromise[] = []; let count = 0; for (let i = 0; i < promises.length; i++) { promises[i].then(data => { result[i] = data; if (++count == promises.length) { resolve(result); } }, error => { reject(error); }); } }); }
let Promise1 = new MyPromise((resolve, reject) => { setTimeout(() => { resolve('Promise1'); }, 2000); }); let Promise2 = new MyPromise((resolve, reject) => { resolve('Promise2'); }); let Promise3 = new MyPromise((resolve, reject) => { resolve('Promise3'); }) let Promise4 = new MyPromise((resolve, reject) => { reject('Promise4'); }) let p = MyPromise.all([Promise1, Promise2, Promise3, Promise4]); p.then((res) => { // 三個都成功則成功 console.log('---成功了', res); }).catch((error) => { // 只要有失敗,則失敗 console.log('---失敗了', err); }); // 直接輸出:---失敗了 Promise4
Promise.race()
方法一樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。
const p = Promise.race([p1, p2, p3]);
只要 p1
、p2
、p3
之中有一個實例率先改變狀態,p
的狀態就跟着改變。那個率先改變的 Promise
實例的返回值,就傳遞給 p
的回調函數。
static race = (promises) => { return new Promise((resolve,reject)=>{ for(let i = 0; i < promises.length; i++){ promises[i].then(resolve,reject) }; }) }
例子和 all
同樣,調用以下:
// ... let p = MyPromise.race([Promise1, Promise2, Promise3, Promise4]) p.then((res) => { console.log('---成功了', res); }).catch((error) => { console.log('---失敗了', err); }); // 直接輸出:---成功了 Promise2
此方法接受一組 Promise
實例做爲參數,包裝成一個新的 Promise
實例。
const p = Promise.race([p1, p2, p3]);
只有等到全部這些參數實例都返回結果,不論是 fulfilled
仍是 rejected
,並且該方法的狀態只可能變成 fulfilled
。
此方法與Promise.all
的區別是all
沒法肯定全部請求都結束,由於在all
中,若是有一個被Promise
被rejected
,p
的狀態就立馬變成rejected
,有可能有些異步請求還沒走完。
static allSettled = (promises: MyPromise[]) => { return new MyPromise((resolve) => { let result: MyPromise[] = []; let count = 0; for (let i = 0; i < promises.length; i++) { promises[i].finally(res => { result[i] = res; if (++count == promises.length) { resolve(result); } }) } }); }
例子和 all
同樣,調用以下:
let p = MyPromise.allSettled([Promise1, Promise2, Promise3, Promise4]) p.then((res) => { // 三個都成功則成功 console.log('---成功了', res); }, err => { // 只要有失敗,則失敗 console.log('---失敗了', err); }) // 2s 後輸出:---成功了 (4) ["Promise1", "Promise2", "Promise3", "Promise4"]
這篇文章筆者帶你們一步一步的實現了符合 Promise/A+
規範的的 Promise
,看完以後相信你們基本上也可以本身獨立寫出一個 Promise
來了。
最後經過幾個問題,你們能夠看看本身掌握的如何:
Promise
中是如何實現回調函數返回值穿透的?Promise
出錯後,是怎麼經過 冒泡 傳遞給最後那個捕獲異常的函數?Promise
如何支持鏈式調用?Promise.then
包裝成一個微任務?實不相瞞,想要個贊!
示例代碼能夠看這裏: