本文分析 Promise 特性的瞭解,完整實現了 Promise 全部功能。沒有參考原生 Promise 的寫法,本身根據思路一步一步完成以及描述,每一個構建模塊由:一、Promise 特性描述;二、實現特性的完整思路(分析一波) 三、項目代碼;四、功能測試代碼 幾個部分組成。大體用到的知識有: 一、變量私有化;二、訂閱發佈模式;三、eventloop 理解;四、Promise特性;五、class 特性;六、對象類型的斷定... 算了不寫了強行塞這麼多我也是夠拼的
開始以前討論一波 class 私有屬性的實現,我的想到的方案以下:java
1.經過閉包,將變量存放在 construct 方法裏;弊端,全部的其餘的對象方法必須在 construct 內定義(NO)。node
2.經過在定義 Promise 的環境下定義一個 Map,根據當前對象索引去獲取相應的私有值;弊端,由於 Map 的 key 是強引用,當定義的 Promise 不用時也不會被內存回收(NO);jquery
3.經過在定義 Promise 的環境下定義一個 WeakMap,根據當前對象索引去獲取相應的私有值; 優點,木有以上兩種劣勢(不寫點什麼感受難受);git
說了這麼多那麼我們要用第三種方法嗎?NO,原生 [[PromiseState]] 是一個內部屬性,不暴露在 Promise 上,可是經過瀏覽器的控制檯能夠看到,用第三種方式模仿並不能直觀的在控制檯看到,因此我決定仍是不要做爲私有變量出現,可是把枚舉特性幹掉了 僞裝他是私有變量 內心好過一點 所以你就能看到下面的代碼;es6
const PENDDING = 'pendding';// 等待狀態 const FULFILLED = 'resolved';// 成功操做狀態 const REJECTED = 'rejected';// 捕獲錯誤狀態 class MyPromise{ constructor(handler){ // 數據初始化 this.init(); } // 數據初始化 init(){ Object.defineProperties(this,{ '[[PromiseState]]': { value: PENDDING, writable: true, enumerable: false }, '[[PromiseValue]]': { value: undefined, writable: true, enumerable: false }, 'thenQueue':{ value: [], writable: true, enumerable: false }, 'catchQueue':{ value: [], writable: true, enumerable: false } }) } // 獲取當前狀態 getPromiseState (){ return this['[[PromiseState]]']; } // 設置當前狀態 setPromiseState (state) { Object.defineProperty(this, '[[PromiseState]]', { value: state, writable: false }) } // 獲取當前值 getPromiseValue (){ return this['[[PromiseValue]]']; } // 設置當前值 setPromiseValue (val) { Object.defineProperty(this, '[[PromiseValue]]', { value: val }) } }
函數調用過程分析:github
resolve
和 reject
兩個方法resolve
時,狀態變爲 fulfilled,有且只有接收一次 resolve
裏的方法裏的值做爲 [[PromiseValue]]
,供該 Promise 對象下的 then
方法使用;reject
時,狀態變爲 rejected,有且只有接收一次 reject
裏的方法裏的值做爲 [[PromiseValue]]
,供該 Promise 對象下的 catch
方法使用;代碼思路:數組
resolve
方法下處理的問題是:promise
一、判斷當前狀態是不是等待狀態,若是不是則啥也不幹,若是是走第二步 二、修改```[[PromiseState]]```爲FULFILLED; 三、將 ```[[PromiseValue]]``` 賦值爲方法傳遞進來的參數; 四、成功操做方法的隊列在 eventloop 結束後<sup>①</sup>依次調用而後清空,捕獲異常方法的隊列清空;
reject
方法基本就不贅述啦......then
方法:瀏覽器
一、 判斷當前狀態是否爲等待,是等待進行第 2 步,不然進行第 3 步; 二、 加入成功操做方法隊列; 三、 當前eventloop 結束異步調用;
catch
方法不贅述ps: 注①由於沒法將任務插入 microtask 中,就用 eventloop結束做爲替代;
// 事件循環最後執行 const eventLoopEndRun = function (handler){ setImmediate(()=>{ handler() }) } // ... class MyPromise{ constructor(handler){ // ... // 方法傳遞,經過 bind 保持兩個方法對當前對象的引用 handler(this.resolve.bind(this), this.reject.bind(this)); } // ... // 清空等待隊列 clearQueue (currentState) { const doQueue = currentState === REJECTED ? this.catchQueue : this.thenQueue; const promiseData = this.getPromiseValue(); doQueue.forEach(queueHandler=>queueHandler(promiseData)); this.catchQueue = []; this.thenQueue = [] } // 狀態改變方法 changeStateHandler (currentState, data){ this.setPromiseState(currentState); this.setPromiseValue(data); setImmediate(()=>{this.clearQueue(currentState)}); // 保持狀態只能改變一次 this.changeStateHandler = null; this.setPromiseState = null; this.setPromiseValue = null; } // 不解釋 resolve (data) { this.changeStateHandler && this.changeStateHandler(FULFILLED, data); } // 不解釋 reject (err) { this.changeStateHandler && this.changeStateHandler(REJECTED, err); } // 不解釋 then(thenHandler){ const currentState = this.getPromiseState(); const promiseData = this.getPromiseValue(); if (currentState === FULFILLED) thenHandler(promiseData); else if (currentState === PENDDING) this.thenQueue.push(thenHandler); } // 不解釋 catch(catchHandler){ const currentState = this.getPromiseState(); const promiseData = this.getPromiseValue(); if (currentState === REJECTED) catchHandler(promiseData); else if (currentState === PENDDING) this.catchQueue.push(catchHandler); } } // 測試方法 const test1 = new MyPromise((resolve,reject)=>{ setTimeout(()=>{ resolve('2s 後輸出了我'); }, 2000) }); const test2 = new MyPromise((resolve,reject)=>{ setTimeout(()=>{ reject('我出錯啦!') }, 2000) }) test1.then(data=>console.log(data)); test1.catch(err=>console.log(err)); test2.then(data=>console.log(data)); test2.catch(err=>console.log(err)); console.log("我是最先的");
經過 Promise.resolve() 建立一個成功操做的 Promise 對象; Promise.reject() 建立一個捕獲錯誤的 Promise 對象,new 關鍵字傳入的方法體有報錯,會直接被 reject 捕獲;
分析一波:
// ... // construct 方法新增一個類型,當 new 關鍵字進來傳遞的不是一個函數,我們一樣在 eventLoop 結束拋出一個錯誤 if(Object.prototype.toString.call(handler) !== "[object Function]"){ eventLoopEndRun(()=>{ throw new Error(`MyPromise resolver ${typeof handler} is not a function`) }) } else { // 方法傳遞,this指向會變,經過 bind 保持兩個方法對當前對象的引用 // 固然也能夠這麼玩:data=>this.resolve(data) try{ handler(this.resolve.bind(this), this.reject.bind(this)); } catch(err) { this.reject(err); } } // ... // 不解釋 static resolve (data) { return new MyPromise(resolve=>resolve(data)); } // 不解釋 static reject (err) { return new MyPromise((resolve, reject)=>{reject(err)}); } // 測試方法 var resolvePromise = MyPromise.resolve(111); resolvePromise.then(data=>console.log(data)); var rejectPromise = MyPromise.reject('這個錯了'); rejectPromise.catch(data=>console.log(data)); new MyPromise(); var errPromise = new MyPromise(()=>{throw new Error("我錯了")}); errPromise.catch(data=>console.log(data.message));
thenable 對象是啥?就是有個屬性爲 then 方法的對象,then 方法裏有兩個參數,resolve、reject 至於 resolve 和 reject 的做用,就不贅述啦 好像仍是打了不少字。
全局錯誤監聽,監聽分爲兩種(書上的說法是): 一個觸發是當前事件循環結束前沒有catch 當前錯誤 Promise --- unhandledRejection;一個觸發是當前事件循環後,當 Promise 被拒絕,而且沒有 catch 程序,就會被觸發 --- rejectionHandled。通過 node 環境下測試(在 Chrome 控制檯測試好像不管如何都不會被觸發)感受是 rejectionHandled 觸發實在新的時間循環添加 catch 程序後纔會被觸發,大體流程圖以下。
let rejected; process.on('unhandledRejection',function(event){ console.log('onunhandledrejection'); }) process.on('rejectionHandled',function(event){ console.log('onrejectionhandled'); }) rejected = Promise.reject(new Error('xx')) eventLoopEndRun(()=>{ console.log(123); rejected.catch(err=>{ console.log(err.message) }) rejected.catch(err=>{ console.log(err.message) }) })
分析一波:
unhanlderReject
事件;unhandledRejection
事件的訂閱,執行傳入 catch 前發佈當前 Promise 的 rejectionHandled
事件。unhanlderReject
事件進行發佈,因此咱們須要調整eventLoopEndRun 函數;當Promise沒有 catch 程序,且沒有全局沒有 unhanlderReject
監聽,咱們就要拋出相應的錯誤。因而乎代碼以下:
// PromiseSubscribePublish.js const UNHANDLEDREJECTION = 'UNHANDLEDREJECTION'; // 當前事件循環,無 catch 函數狀態; const REJECTIONHANDLED = 'REJECTIONHANDLED'; // 事件循環後,無 catch 函數狀態; class PromiseSubscribePublish{ constructor(){ this.subscribeUnhandler = new Map(); this.subscribeHandler = new Map(); this.errFuc = {} } // 監聽事件綁定 bindLisener (type, cb){ console.log(type.toUpperCase(), UNHANDLEDREJECTION) if(type.toUpperCase() !== UNHANDLEDREJECTION && type.toUpperCase() !== REJECTIONHANDLED) throw Error('type toUpperCase must be UNHANDLEDREJECTION or REJECTIONHANDLED'); if(Object.prototype.toString.call(cb) !== "[object Function]") throw Error('callback is not function'); this.errFuc[type.toUpperCase()] = cb; } subscribe(promise, err){ // 訂閱一波,以當前 Promise 爲 key,err 爲參數,加入 unhandler map 中 this.subscribeUnhandler.set(promise, err) } quitSubscribe(promise){ this.subscribeUnhandler.delete(promise); } publish (type, promise) { let changgeStateFuc; // 定義當前狀態變換操做 const errFuc = this.errFuc[type]; // 當前綁定的監聽函數 if(type === UNHANDLEDREJECTION){ // 沒有訂閱事件的 promise 則啥也不幹 if (!this.subscribeUnhandler.size) return; // 根據當前事件類型,選擇處理函數 changgeStateFuc = (err, promise)=>{ this.subscribeHandler.set(promise); this.subscribeUnhandler.delete(promise, err); } // 不論如何當前時間循環下的等待隊列狀態所有須要變動 if(errFuc){ this.subscribeUnhandler.forEach((err, promise)=>{ errFuc(err, promise) changgeStateFuc(err, promise) }) } else { this.subscribeUnhandler.forEach((err, promise)=>{ changgeStateFuc(err, promise) }) console.error('Uncaught (in promise)', err); } } else { // 若是該 promise 沒有進行訂閱 if(!this.subscribeHandler.has(promise)) return; // 哪一個 promise 發佈 catch 函數,就根據當前 Promise 執行相應方法,並將其從 Handler 訂閱者裏刪除 errFuc && errFuc(promise); this.subscribeHandler.delete(promise); } } } // 定義一些靜態成員變量 默認不可寫 Object.defineProperties(PromiseSubscribePublish, { [UNHANDLEDREJECTION]:{ value: UNHANDLEDREJECTION }, [REJECTIONHANDLED]:{ value: REJECTIONHANDLED } }) module.exports = PromiseSubscribePublish; // MyPromise.js // .. const PromiseSubscribePublish = require('./PromiseSubscribePublish'); const promiseSubscribePublish = new PromiseSubscribePublish(); // 事件循環最後執行 const eventLoopEndRun = (()=>{ let unhandledPub; let timer; const queueHandler = []; // 激活事件循環最後執行 const activateRun = ()=>{ // 截流 timer && clearTimeout(timer); timer = setTimeout(()=>{ unhandledPub && unhandledPub(); let handler = queueHandler.shift(); while(handler){ handler(); handler = queueHandler.shift(); } },0); } // 設置 unhanldedReject 優先級最高 , 直接加入隊列 return (handler,immediate)=> { immediate ? unhandledPub = handler : queueHandler.push(handler); activateRun(); } })() //... reject (err) { this.changeStateHandler && this.changeStateHandler(REJECTED, err); promiseSubscribePublish.subscribe(this, err); // 存在 reject ,事件循環結束髮布 UNHANDLEDREJECTION eventLoopEndRun(()=> promiseSubscribePublish.publish(PromiseSubscribePublish.UNHANDLEDREJECTION, this), true ); } //... static unhandledRejectionLisener(cb){ promiseSubscribePublish.bindLisener(PromiseSubscribePublish.UNHANDLEDREJECTION ,cb) } static rejectionHandledLisener(cb){ promiseSubscribePublish.bindLisener(PromiseSubscribePublish.REJECTIONHANDLED ,cb) } // ... catch(catchHandler){ const currentState = this.getPromiseState(); const promiseData = this.getPromiseValue(); // 取消當前事件循環下 reject 狀態未 catch 事件訂閱; promiseSubscribePublish.quitSubscribe(this); if (currentState === REJECTED) { eventLoopEndRun(()=>{ // 發佈 catch 處理 promiseSubscribePublish.publish(PromiseSubscribePublish.REJECTIONHANDLED, this); catchHandler(promiseData); }); } else if (currentState === PENDDING) this.catchQueue.push(catchHandler); } // 測試代碼 MyPromise.unhandledRejectionLisener((err,promise)=>{ console.log(err, promise); }) MyPromise.rejectionHandledLisener((err,promise)=>{ console.log(err, promise); }) var myPromise = MyPromise.reject(11); // myPromise.catch(()=>{console.log('catch')}); setTimeout(()=>{ myPromise.catch(()=>{console.log('catch')}); },1000)
看到鏈式,首先想到的是 jquery 調用。jquery 返回的是 jquery 對象本體。而 Promise 根據狀態判斷:
[[PromiseStatus]]
和 [[PromiseValues]]
狀態相同新構建的 Promise;調用 then 方法時,返回和當前 Promise 的 [[PromiseStatus]]
相同的,[[PromiseValues]]
值爲 then 方法返回值的 新構建的 Promise;[[PromiseStatus]]
和 [[PromiseValues]]
狀態相同新構建的 Promise;調用 catch 方法時, 返回操做成功的新構建的 Promise ,[[PromiseValues]]
值爲 catch 方法返回值;[[PromiseValues]]
爲那個錯誤;unhandledRejuect
訂閱中去除。unhandledRejuect
訂閱是當前事件循環,若是鏈上有方法報錯,unhandledRejuect
訂閱會再次發生,這樣會形成哪怕當前報錯 Promise 後有 catch,也會拋出錯誤,所以須要給當前 Promise 加一個屬性,以標誌鏈後有 catch,使得其不訂閱 unhandledRejuect
事件。分析一波:
1. 要在實例方法中,建立另外一個當前類的實例時,必須用到當前類的構造函數。當我們的類被繼承出一個派生類,我們但願返回的是那個派生類,因而不能直接 new MyPromise 去建立,而要使用一個 Symbol.species 2. 新建 *Promise* 和以前的 *Promise* 存在關聯,因此當前 *Promise* 的狀態決定新 *Promise* 狀態,構建新 *Promise* 的過程當中當前 *Promise* 的捕獲函數不能將其訂閱從 unhandledReject 中移除,因此須要一個標誌位來標識 then 函數屬性。 3. *Promise* 鏈上若是出現 catch 函數,鏈上 catch 函數以前的全部 *Promise* 都將從訂閱 unhandledReject Map 中移除,所以 *Promise* 須要記錄鏈上的上一級 *Promise*; 4. *Promise* then 或 catch 方法體內報錯將構建一個捕獲錯誤狀態的 *Promise*,所以須要一個函數去捕獲可能發生的錯誤;
//... MyPromise.js const runFucMaybeError = handler => { try { return handler(); } catch(err) { return { iserror: FUCERROR, err }; } } const clearLinksSubscribe = linkPrePromise=>{ while(linkPrePromise && !linkPrePromise.hascatch){ linkPrePromise.hascatch = true; promiseSubscribePublish.quitSubscribe(linkPrePromise); linkPrePromise = linkPrePromise.linkPrePromise; } } // 不解釋 then(thenHandler, quitReturn){ const currentState = this.getPromiseState(); const promiseData = this.getPromiseValue(); let nextPromiseData; if (currentState === FULFILLED) eventLoopEndRun(()=>{ nextPromiseData = runFucMaybeError(()=>thenHandler(promiseData)) }); else if (currentState === PENDDING) this.thenQueue.push(data=>{ nextPromiseData = runFucMaybeError(()=>thenHandler(data)) }); if(!quitReturn){ const nextPromise = new this.constructor[Symbol.species]((resolve,reject)=>{ this.catch(err=>{ reject(err); }, true); // 根據隊列原則,執行確定在當前 then 後,保證能正確拿到前一個 Promise 的返回值 this.then(()=>{ nextPromiseData && nextPromiseData.iserror === FUCERROR ? reject(nextPromiseData.err) : resolve(nextPromiseData) }, true) }) nextPromise.linkPrePromise = this; return nextPromise; }; } catch(catchHandler, quitReturn){ const currentState = this.getPromiseState(); const promiseData = this.getPromiseValue(); let nextPromiseData; // 取消當前事件循環下 reject 狀態未 catch 事件訂閱; // 當是實例內部調用時,不能將當前 Promise 從 unhandledReject 隊列中移除; // 不然順着生成鏈依次將 Promise 移除; if(!quitReturn)clearLinksSubscribe(this) if (currentState === REJECTED) { eventLoopEndRun(()=>{ // 發佈 catch 處理 promiseSubscribePublish.publish(PromiseSubscribePublish.REJECTIONHANDLED, this); nextPromiseData = runFucMaybeError(()=>catchHandler(promiseData)); }); } else if (currentState === PENDDING) this.catchQueue.push(data=>{ nextPromiseData = runFucMaybeError(()=>{catchHandler(data)}) }); if(!quitReturn){ const nextPromise = new this.constructor[Symbol.species]((resolve,reject)=>{ // 根據隊列原則,執行確定在當前 then 後,保證能正確拿到報錯的 Promise 的返回值 this.catch(()=>{ nextPromiseData && nextPromiseData.iserror === FUCERROR ? reject(nextPromiseData.err) : resolve(nextPromiseData) }, true); this.then(data=>resolve(data), true) }) nextPromise.linkPrePromise = this; return nextPromise; } } // 測試代碼 const test1 = new MyPromise((resolve,reject)=>{ setTimeout(()=>{ resolve('2s 後輸出了我'); }, 2000) }); test1.then(data=>{ console.log(data); return '你好' }).then(data=>{ console.log(data); return '很差' }).then(data=>{ console.log(data); }); test1.catch(err=>console.log(err)).then(data=>{ console.log(data); return 'gggg' }).then(data=>{ console.log(data); }); const test2 = new MyPromise((resolve,reject)=>{ throw new Error('xx'); }) test2.then(data=>console.log(data)).catch(err=>console.log(err)); test2.catch(err=>console.log(err)).then(data=>{ console.log(data); return '你好' }).then(data=>{ console.log(data); return '很差' }).then(data=>{ console.log(data); }); var a = MyPromise.resolve(1); var b = a.then(data=>{throw new Error('11')}).catch(err=>{console.log(err.message)})
Promise.all 有以下特性: 一、接收一個具備[Symbol.iterator]函數的數據, 返回一個 Promise,該 Promise 成功操做,then 方法傳入一個數組,數組數據位置和迭代器迭代返回的順序相關聯,該 Promise 捕獲錯誤 catch 裏的傳入捕獲的錯誤; 二、 迭代器遍歷結果若是是 Promise , 則將其 PromiseValue 做爲值,插入傳入數組對應的位置,當遍歷結果不是 Promise 直接插入數組對應位置,當遇到捕獲錯誤,或者 Promise 出現錯誤時直接將狀態轉變爲 rejected 狀態 ,從 catch 拿到相應錯誤的值;總結就是有錯立刻拋,要不等全部數據處理完才改變狀態;
Promise.race 就不贅述:記住幾點,傳入參數要求和 .all 相同,數據處理方式是,先到先得,率先處理完的數據直接修改狀態。
在分析一波以前,調整幾個以前的沒有考慮到的問題:
unhandledReject
事件,由於 then 方法也會生成新的 Promise,而 then 鏈前有捕獲異常狀態的 Promise 會形成重複報錯,catch 無所謂,由於自己會Promise 鏈隊列。// 開頭的 '-' 標示移除,'+' 表示新增 // ... changeStateHandler 方法 - this.changeStateHandler = null; resolve (data) { if(this.changeStateHandler){ this.changeStateHandler(FULFILLED, data); // 保持狀態只能改變一次 this.changeStateHandler = null; } } reject (err, noSubscribe) { if(this.changeStateHandler){ this.changeStateHandler(REJECTED, err); !noSubscribe && !this.hascatch && promiseSubscribePublish.subscribe(this, err); // 存在 reject ,事件循環結束髮布 UNHANDLEDREJECTION eventLoopEndRun(()=> promiseSubscribePublish.publish(PromiseSubscribePublish.UNHANDLEDREJECTION, this), true ); // 保持狀態只能改變一次 this.changeStateHandler = null; } } // then 方法 - this.catch(err=>{ reject(err) }, true); + this.catch(err=>reject(err, true), true);
接下來開始分析一波:
Symbol.iterator
,沒有就直接拋錯(Promise 狀態會直接變爲 reject,就不往下說了);Object.prototype.toString.call
去判斷,我們須要給我們的類加一個 tag// MyPromise.js 最後頭 MyPromise.prototype[Symbol.toStringTag] = "MyPromise"; static all (promiseArr){ // 由於是靜態方法 沒法獲取 this 因此不能使用實例內部方法構建方式去構建新對象 return new MyPromise((resolve,reject)=>{ const iterator = isIterator(promiseArr); if(typeof iterator === 'string'){ console.error(iterator); throw new Error(iterator); } let data = iterator.next(); const result = []; let index = -1; // Promise 應存放返回數組的位置; let waitPromiseNum = 0; // 統計未完成的 Promise; let checkAllEnd = () => { return waitPromiseNum === 0; } while (data) { if(data.done) break; index ++; if(Object.prototype.toString.call(data.value) !== "[object MyPromise]"){ result[index] = data.value; } else { (index=>{ const promise = data.value; waitPromiseNum++; promise.then(data=>{ result[index] = data; waitPromiseNum--; // 看是否 Promise 所有完成 if(checkAllEnd())resolve(result); }).catch(data=>reject(data)); })(index) } data = iterator.next(); } if(checkAllEnd())resolve(result); }) } static race (promiseArr){ // 由於是靜態方法 沒法獲取 this 因此不能使用實例內部方法構建方式去構建新對象 return new MyPromise((resolve,reject)=>{ const iterator = isIterator(promiseArr); if(typeof iterator === 'string'){ console.error(iterator); throw new Error(iterator); } let data = iterator.next(); while (data) { if(data.done) break; if(Object.prototype.toString.call(data.value) !== "[object MyPromise]"){ return resolve(data.value); } else { data.value .then(data=>resolve(data)) .catch(data=>reject(data)); } data = iterator.next(); } }) } // 測試方法 MyPromise.all( [ MyPromise.resolve(1), new MyPromise(resolve=>setTimeout(()=>resolve(2), 1000)), MyPromise.resolve(3) ]).then(data=>{console.log(data)}); MyPromise.all([ 1, new MyPromise(resolve=>setTimeout(()=>resolve(2), 1000)), MyPromise.resolve(3) ]).then(data=>{console.log(data)}); MyPromise.all([ MyPromise.resolve(1), new MyPromise(resolve=>setTimeout(()=>resolve(2), 1000)), MyPromise.reject(3) ]).then(data=>{console.log(data)}); MyPromise.race([ MyPromise.resolve(1), new MyPromise(resolve=>setTimeout(()=>resolve(2), 1000)), MyPromise.resolve(3) ]).then(data=>{console.log(data)}); MyPromise.race([ 1, new MyPromise(resolve=>setTimeout(()=>resolve(2), 1000)), MyPromise.resolve(3) ]).then(data=>{console.log(data)}); MyPromise.race([ MyPromise.resolve(1), new MyPromise(resolve=>setTimeout(()=>resolve(2), 1000)), MyPromise.reject(3) ]).then(data=>{console.log(data)});
若是發現過程遇到什麼問題,歡迎及時提出。