-- What i can't create, i don't understantgit
實現Promise的目的是爲了深刻的理解Promies,以在項目中游刃有餘的使用它。完整的代碼見gitHubgithub
Promise的標準有不少個版本,本文采用ES6原生Promise使用的Promise/A+標準。完整的Promise/A+標準見這裏,總結以下:
promise
OK,搞清楚了promise標準以後,開始動手吧瀏覽器
產生一個對象有不少種方法,構造函數是看起來最面向對象的一種,並且原生Promise實現也是使用的構造函數,所以我也決定使用構造函數的方法。
首先,先寫一個大概的框架出來:閉包
// 總所周知,Promise傳入一個executor,有兩個參數resolve, reject,用來改變promise的狀態 function Promise(executor) { this.status = 'pending' this.value = void 0 // 爲了方便把value和reason合併 const resolve = function() {} const reject = function() {} executor(resolve, reject) }
很明顯,這個構造函數還有不少問題們一個一個來看框架
resolve和reject並無什麼卵用。
首先,用過promise的都知道,resolve和reject是用來改變promise的狀態的:異步
function Promise(executor) { this.status = 'pending' this.value = void 0 // 爲了方便把value和reason合併 const resolve = value => { this.value = value this.status = 'resolved' } const reject = reason => { this.value = reason this.status = 'rejected' } executor(resolve, reject) }
而後,當resolve或者reject調用的時候,須要執行在then方法裏傳入的相應的函數(通知)。有沒有以爲這個有點相似於事件(發佈-訂閱模式)呢?函數
function Promise(executor) { this.status = 'pending' this.value = void 0 // 爲了方便把value和reason合併 this.resolveListeners = [] this.rejectListeners = [] // 通知狀態改變 const notify(target, val) => { target === 'resolved' ? this.resolveListeners.forEach(cb => cb(val)) : this.rejectListeners.forEach(cb => cb(val)) } const resolve = value => { this.value = value this.status = 'resolved' notify('resolved', value) } const reject = reason => { this.value = reason this.status = 'rejected' notify('rejected', reason) } executor(resolve, reject) }
status和value並無作到一旦肯定,沒法更改。這裏有兩個問題,一是返回的對象暴露了status和value屬性,而且能夠隨意賦值;二是若是在executor裏屢次調用resolve或者reject,會使value更改屢次。
第一個問題,如何實現只讀屬性:
測試
function Promise(executor) { if (typeof executor !== 'function') { throw new Error('Promise executor must be fucntion') } let status = 'pending' // 閉包造成私有屬性 let value = void 0 ...... // 使用status代替this.value const resolve = val => { value = val status = 'resolved' notify('resolved', val) } const reject = reason => { value = reason status = 'rejected' notify('rejected', reason) } // 經過getter和setter設置只讀屬性 Object.defineProperty(this, 'status', { get() { return status }, set() { console.warn('status is read-only') } }) Object.defineProperty(this, 'value', { get() { return value }, set() { console.warn('value is read-only') } })
第二個問題,避免屢次調用resolve、reject時改變value,並且標準裏(2.2.2.3 it must not be called more than once)也有規定,then註冊的回調只能執行一次。
ui
const resolve = val => { if (status !== 'pending') return // 避免屢次運行 value = val status = 'resolved' notify('resolved', val) }
then註冊的回調須要異步執行。
說到異步執行,對原生Promise有了解的同窗都知道,then註冊的回調在Micro-task中,而且調度策略是,Macro-task中執行一個任務,清空全部Micro-task的任務。簡而言之,promise異步的優先級更高。
其實,標準只規定了promise回調須要異步執行,在一個「乾淨的」執行棧執行,並無規定必定說要用micro-task,而且在低版本瀏覽器中,並無micro-task隊列。不過在各類promise的討論中,因爲原生Promise的實現,micro-task已經成成爲了事實標準,並且promise回調在micro-task中也使得程序的行爲更好預測。
在瀏覽器端,能夠用MutationObserver實現Micro-task。本文利用setTimeout來簡單實現異步。
const resolve = val => { if (val instanceof Promise) { return val.then(resolve, reject) } // 異步執行 setTimeout(() => { if (status !== 'pending') return status = 'resolved' value = val notify('resolved', val) }, 0) }
最後,加上錯誤處理,就獲得了一個完整的Promise構造函數:
function Promise(executor) { if (typeof executor !== 'function') { throw new Error('Promise executor must be fucntion') } let status = 'pending' let value = void 0 const notify = (target, val) => { target === 'resolved' ? this.resolveListeners.forEach(cb => cb(val)) : this.rejectListeners.forEach(cb => cb(val)) } const resolve = val => { if (val instanceof Promise) { return val.then(resolve, reject) } setTimeout(() => { if (status !== 'pending') return status = 'resolved' value = val notify('resolved', val) }, 0) } const reject = reason => { setTimeout(() => { if (status !== 'pending') return status = 'rejected' value = reason notify('rejected', reason) }, 0) } this.resolveListeners = [] this.rejectListeners = [] Object.defineProperty(this, 'status', { get() { return status }, set() { console.warn('status is read-only') } }) Object.defineProperty(this, 'value', { get() { return value }, set() { console.warn('value is read-only') } }) try { executor(resolve, reject) } catch (e) { reject(e) } }
總的來講,Promise構造函數其實只幹了一件事:執行傳入的executor,並構造了executor的兩個參數。
首先須要肯定的是,then方法是寫在構造函數裏仍是寫在原型裏。
寫在構造函數了裏有一個比較大的好處:能夠像處理status和value同樣,經過閉包讓resolveListeners和rejectListeners成爲私有屬性,避免經過this.rejectListeners來改變它。
寫在構造函數裏的缺點是,每個promise對象都會有一個不一樣的then方法,這既浪費內存,又不合理。個人選擇是寫在原型裏,爲了保持和原生Promise有同樣的結構和接口。
ok,仍是先寫一個大概的框架:
Promise.prototype.then = function (resCb, rejCb) { this.resolveListeners.push(resCb) this.rejectListeners.push(rejCb) return new Promise() }
隨後,一步一步的完善它:
then方法返回的promise須要根據resCb或rejCb的運行結果來肯定狀態。
Promise.prototype.then = function (resCb, rejCb) { return new Promise((res, rej) => { this.resolveListeners.push((val) => { try { const x = resCb(val) res(x) // 以resCb的返回值爲value來resolve } catch (e) { rej(e) // 若是出錯,返回的promise以異常爲reason來reject } }) this.rejectListeners.push((val) => { try { const x = rejCb(val) res(x) // 注意這裏也是res而不是rej哦 } catch (e) { rej(e) // 若是出錯,返回的promise以異常爲reason來reject } }) }) }
ps:衆所周知,promise能夠鏈式調用,提及鏈式調用,個人第一個想法就是返回this就能夠了,可是then方法不能夠簡單的返回this,而要返回一個新的promise對象。由於promise的狀態一旦肯定就不能更改,而then方法返回的promise的狀態須要根據then回調的運行結果來決定。
若是resCb/rejCb返回一個promiseA,then返回的promise須要跟隨(adopt)promiseA,也就是說,須要保持和promiseA同樣的status和value。
this.resolveListeners.push((val) => { try { const x = resCb(val) if (x instanceof Promise) { x.then(res, rej) // adopt promise x } else { res(x) } } catch (e) { rej(e) } }) this.rejectListeners.push((val) => { try { const x = resCb(val) if (x instanceof Promise) { x.then(res, rej) // adopt promise x } else { res(x) } } catch (e) { rej(e) } })
若是then的參數不是函數,須要忽略它,相似於這種狀況:
new Promise(rs => rs(5)) .then() .then(console.log)
其實就是把value和狀態日後傳遞
this.resolveListeners.push((val) => { if (typeof resCb !== 'function') { res(val) return } try { const x = resCb(val) if (x instanceof Promise) { x.then(res, rej) // adopt promise x } else { res(x) } } catch (e) { rej(e) } }) // rejectListeners也是相同的邏輯
若是調用then時, promise的狀態已經肯定,相應的回調直接運行
// 注意這裏須要異步 if (status === 'resolved') setTimeout(() => resolveCb(value), 0) if (status === 'rejected') setTimeout(() => rejectCb(value), 0)
最後,就獲得了一個完整的then方法,總結一下,then方法幹了兩件事,一是註冊了回調,二是返回一個新的promise對象。
// resolveCb和rejectCb是相同的邏輯,封裝成一個函數 const thenCallBack = (cb, res, rej, target, val) => { if (typeof cb !== 'function') { target === 'resolve' ? res(val) : rej(val) return } try { const x = cb(val) if (x instanceof Promise) { x.then(res, rej) // adopt promise x } else { res(x) } } catch (e) { rej(e) } } Promise.prototype.then = function (resCb, rejCb) { const status = this.status const value = this.value let thenPromise thenPromise = new Promise((res, rej) => { /** * 這裏不能使用bind來實現柯里畫,規範裏規定了: * 2.2.5: onFulfilled and onRejected must be called as functions (i.e. with no this value)) */ const resolveCb = val => { thenCallBack(resCb, res, rej, 'resolve', val) } const rejectCb = val => { thenCallBack(rejCb, res, rej, 'reject', val) } if (status === 'pending') { this.resolveListeners.push(resolveCb) this.rejectListeners.push(rejectCb) } if (status === 'resolved') setTimeout(() => resolveCb(value), 0) if (status === 'rejected') setTimeout(() => rejectCb(value), 0) }) return thenPromise }
首先要明白的是什麼叫互相調用,什麼狀況下會互相調用。以前實現then方法的時候,有一條規則是:若是then方法的回調返回一個promiseA。then返回的promise須要adopt這個promiseA,也就是說,須要處理這種狀況:
new MyPromise(rs => rs(5)) .then(val => { return Promise.resolve(5) // 原生Promise }) .then(val => { return new Bluebird(r => r(5)) // Bluebird的promise })
關於這個,規範裏定義了一個叫作The Promise Resolution Procedure的過程,咱們須要作的就是把規範翻譯一遍,並替代代碼中判斷promise的地方
const resolveThenable = (promise, x, resolve, reject) => { if (x === promise) { return reject(new TypeError('chain call found')) } if (x instanceof Promise) { return x.then(v => { resolveThenable(promise, v, resolve, reject) }, reject) } if (x === null || (typeof x !== 'object' && typeof x !== 'function')) { return resolve(x) } let called = false try { // 這裏有一個有意思的技巧。標準裏解釋了,若是then是一個getter,那麼經過賦值能夠保證getter只被觸發一次,避免反作用 const then = x.then if (typeof then !== 'function') { return resolve(x) } then.call(x, v => { if (called) return called = true resolveThenable(promise, v, resolve, reject) }, r => { if (called) return called = true reject(r) }) } catch (e) { if (called) return reject(e) } }
到這裏,一個符合標準的Promise就完成了,完整的代碼以下:
function Promise(executor) { if (typeof executor !== 'function') { throw new Error('Promise executor must be fucntion') } let status = 'pending' let value = void 0 const notify = (target, val) => { target === 'resolved' ? this.resolveListeners.forEach(cb => cb(val)) : this.rejectListeners.forEach(cb => cb(val)) } const resolve = val => { if (val instanceof Promise) { return val.then(resolve, reject) } setTimeout(() => { if (status !== 'pending') return status = 'resolved' value = val notify('resolved', val) }, 0) } const reject = reason => { setTimeout(() => { if (status !== 'pending') return status = 'rejected' value = reason notify('rejected', reason) }, 0) } this.resolveListeners = [] this.rejectListeners = [] Object.defineProperty(this, 'status', { get() { return status }, set() { console.warn('status is read-only') } }) Object.defineProperty(this, 'value', { get() { return value }, set() { console.warn('value is read-only') } }) try { executor(resolve, reject) } catch (e) { reject(e) } } const thenCallBack = (cb, res, rej, target, promise, val) => { if (typeof cb !== 'function') { target === 'resolve' ? res(val) : rej(val) return } try { const x = cb(val) resolveThenable(promise, x, res, rej) } catch (e) { rej(e) } } const resolveThenable = (promise, x, resolve, reject) => { if (x === promise) { return reject(new TypeError('chain call found')) } if (x instanceof Promise) { return x.then(v => { resolveThenable(promise, v, resolve, reject) }, reject) } if (x === null || (typeof x !== 'object' && typeof x !== 'function')) { return resolve(x) } let called = false try { // 這裏有一個有意思的技巧。標準裏解釋了,若是then是一個getter,那麼經過賦值能夠保證getter只被觸發一次,避免反作用 const then = x.then if (typeof then !== 'function') { return resolve(x) } then.call(x, v => { if (called) return called = true resolveThenable(promise, v, resolve, reject) }, r => { if (called) return called = true reject(r) }) } catch (e) { if (called) return reject(e) } } Promise.prototype.then = function (resCb, rejCb) { const status = this.status const value = this.value let thenPromise thenPromise = new Promise((res, rej) => { const resolveCb = val => { thenCallBack(resCb, res, rej, 'resolve', thenPromise, val) } const rejectCb = val => { thenCallBack(rejCb, res, rej, 'reject', thenPromise, val) } if (status === 'pending') { this.resolveListeners.push(resolveCb) this.rejectListeners.push(rejectCb) } if (status === 'resolved') setTimeout(() => resolveCb(value), 0) if (status === 'rejected') setTimeout(() => rejectCb(value), 0) }) return thenPromise }
實現Promise的過程其實並無我預想的那麼難,所謂的Promise的原理我感受就是相似於觀察者模式,so,不要有畏難情緒,我上我也行^_^。