手寫Promise一直是前端童鞋很是頭疼的問題,也是面試的高頻題。網上有不少手寫Promise的博客,但大部分都存在或多或少的問題。
下面咱們根據A+規範,手寫一個Promise前端
在此部分,先把Promise的基礎結構寫出來。
直接上代碼node
// 規範2.1:A promise must be in one of three states: pending, fulfilled, or rejected. // 三個狀態:PENDING、FULFILLED、REJECTED const PENDING = Symbol(); const FULFILLED = Symbol(); const REJECTED = Symbol(); // 根據規範2.2.1到2.2.3 class _Promise { constructor(executor) { // 默認狀態爲 PENDING this.status = PENDING; // 存放成功狀態的值,默認爲 undefined this.value = undefined; // 存放失敗狀態的值,默認爲 undefined this.reason = undefined; // 成功時,調用此方法 let resolve = (value) => { // 狀態爲 PENDING 時才能夠更新狀態,防止 executor 中調用了兩次 resovle/reject 方法 if (this.status === PENDING) { this.status = FULFILLED; this.value = value; } }; // 失敗時,調用此方法 let reject = (reason) => { // 狀態爲 PENDING 時才能夠更新狀態,防止 executor 中調用了兩次 resovle/reject 方法 if (this.status === PENDING) { this.status = REJECTED; this.reason = reason; } }; try { // 當即執行,將 resolve 和 reject 函數傳給使用者 executor(resolve, reject); } catch (error) { // 發生異常時執行失敗邏輯 reject(error); } } // 包含一個 then 方法,並接收兩個參數 onFulfilled、onRejected then(onFulfilled, onRejected) { if (this.status === FULFILLED) { onFulfilled(this.value); } if (this.status === REJECTED) { onRejected(this.reason); } } } export default _Promise;
接下來咱們用測試代碼測一下面試
const promise = new _Promise((resolve, reject) => { resolve('成功'); setTimeout(() => { console.log('settimeout1'); }, 0); }) .then( (data) => { console.log('success', data); setTimeout(() => { console.log('settimeout2'); }, 0); }, (err) => { console.log('faild', err); } ) .then((data) => { console.log('success2', data); });
控制檯打印
能夠看到,在executor方法中的異步行爲在最後才執行
並且若是把resolve方法放到setTimeout中,會沒法執行
這固然是不妥的。
接下來咱們優化一下異步數組
在上一小節中,咱們將resolve的結果值存放到了this.value裏。
優化後的代碼以下:promise
// 規範2.1:A promise must be in one of three states: pending, fulfilled, or rejected. // 三個狀態:PENDING、FULFILLED、REJECTED const PENDING = Symbol(); const FULFILLED = Symbol(); const REJECTED = Symbol(); class _Promise { constructor(executor) { this.status = PENDING; this.value = undefined; this.reason = undefined; // 存放成功的回調 this.onResolvedCallbacks = []; // 存放失敗的回調 this.onRejectedCallbacks = []; // 這裏使用數組,是由於若是屢次調用then,會把方法都放到數組中。 // 可是目前這個版本還不支持then的鏈式調用 let resolve = (value) => { if (this.status === PENDING) { this.status = FULFILLED; this.value = value; // 依次將對應的函數執行 // 在此版本中,這個數組實際上長度最多隻爲1 this.onResolvedCallbacks.forEach(fn => fn()); } } let reject = (reason) => { if (this.status === PENDING) { this.status = REJECTED; this.reason = reason; // 依次將對應的函數執行 // 在此版本中,這個數組實際上長度最多隻爲1 this.onRejectedCallbacks.forEach(fn => fn()); } } try { executor(resolve, reject) } catch (error) { reject(error) } } then(onFulfilled, onRejected) { if (this.status === FULFILLED) { onFulfilled(this.value) } if (this.status === REJECTED) { onRejected(this.reason) } // 上面兩個分支是:支持resolve函數執行的時候,若是不在異步行爲裏執行resolve的話,會當即執行onFulfilled方法 if (this.status === PENDING) { // 若是promise的狀態是 pending,須要將 onFulfilled 和 onRejected 函數存放起來,等待狀態肯定後,再依次將對應的函數執行 this.onResolvedCallbacks.push(() => { onFulfilled(this.value) }); // 若是promise的狀態是 pending,須要將 onFulfilled 和 onRejected 函數存放起來,等待狀態肯定後,再依次將對應的函數執行 this.onRejectedCallbacks.push(() => { onRejected(this.reason); }) } } }
咱們用測試方法測一下:瀏覽器
const promise = new _Promise((resolve, reject) => { setTimeout(() => { console.log('settimeout1'); resolve('成功'); }, 0); }) .then( (data) => { console.log('success', data); setTimeout(() => { console.log('settimeout2'); }, 0); return data; }, (err) => { console.log('faild', err); } ) .then((data) => { console.log('success2', data); });
控制檯結果:
能夠看到,異步順序是正確的,先執行settimeout1,再執行success
可是不支持鏈式的then調用,也不支持在then中返回一個新的Promisedom
接下來咱們將完整實現一個支持鏈式調用的Promis異步
// 規範2.1:A promise must be in one of three states: pending, fulfilled, or rejected. // 三個狀態:PENDING、FULFILLED、REJECTED const PENDING = Symbol(); const FULFILLED = Symbol(); const REJECTED = Symbol(); class _Promise { constructor(executor) { this.status = PENDING; this.value = undefined; this.reason = undefined; // 存放成功的回調 this.onResolvedCallbacks = []; // 存放失敗的回調 this.onRejectedCallbacks = []; // 這裏使用數組,是由於若是屢次調用then,會把方法都放到數組中。 // 可是目前這個版本還不支持then的鏈式調用 let resolve = (value) => { if (this.status === PENDING) { this.status = FULFILLED; this.value = value; // 依次將對應的函數執行 // 在此版本中,這個數組實際上長度最多隻爲1 this.onResolvedCallbacks.forEach(fn => fn()); } } let reject = (reason) => { if (this.status === PENDING) { this.status = REJECTED; this.reason = reason; // 依次將對應的函數執行 // 在此版本中,這個數組實際上長度最多隻爲1 this.onRejectedCallbacks.forEach(fn => fn()); } } try { // 當即執行executor方法 executor(resolve, reject) } catch (error) { reject(error) } } // 這裏就是最關鍵的then方法 then(onFulfilled, onRejected) { // 克隆this,由於以後的this就不是原promise的this了 const self = this; // 判斷兩個傳入的方法是否是funcion,若是不是,那麼給一個function的初始值 onnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function'?onRejected:reason => { throw new Error(reason instanceof Error ? reason.message:reason) } // 返回一個新的promise,剩下的邏輯都在這個新的promise裏進行 return new _Promise((resolve, reject) => { if (this.status === PENDING) { // 若是promise的狀態是 pending,須要將 onFulfilled 和 onRejected 函數存放起來,等待狀態肯定後,再依次將對應的函數執行 self.onResolvedCallbacks.push(() => { // 使用settimeout模擬微任務 setTimeout((0 => { // self.value是以前存在value裏的值 const result = onFulfilled(self.value); // 這裏要考慮兩種狀況,若是onFulfilled返回的是Promise,則執行then // 若是返回的是一個值,那麼直接把值交給resolve就行 result instanceof _Promise ? result.then(resolve, reject) : resolve(result); }, 0) onFulfilled(self.value) }); // 若是promise的狀態是 pending,須要將 onFulfilled 和 onRejected 函數存放起來,等待狀態肯定後,再依次將對應的函數執行 // reject也要進行同樣的事 self.onRejectedCallbacks.push(() => { setTimeout(() => { const result = onRejected(self.reason); // 不一樣點:此時是reject result instanceof _Promise ? result.then(resolve, reject) : reject(result); }, 0) }) } // 若是不是PENDING狀態,也須要判斷是否是promise的返回值 if (self.status === FULFILLED) { setTimeout(() => { const result = onFulfilled(self.value); result instanceof _Promise ? result.then(resolve, reject) : resolve(result); }); } if (self.status === REJECTED) { setTimeout(() => { const result = onRejected(self.reason); result instanceof _Promise ? result.then(resolve, reject) : reject(result); }) } }) // 到這裏,最難的then方法已經寫完了 } }
catch方法的通常用法是new _Promise(() => {...}).then(() => {...}).catch(e => {...})
因此它是一個和then同級的方法,它實現起來很是簡單:函數
class _Promise{ ... catch(onRejected) { return this.then(null, onRejected); } }
靜態resolve、靜態reject的用法:_Promise.resolve(() => {})
這樣能夠直接返回一個_Promise
這塊的實現,參考then中返回_Promise的那一段,就能實現
reject相似測試
class _Promise{ ... static resolve(value) { if (value instanceof Promise) { // 若是是Promise實例,直接返回 return value; } else { // 若是不是Promise實例,返回一個新的Promise對象,狀態爲FULFILLED return new Promise((resolve, reject) => resolve(value)); } } static reject(reason) { return new Promise((resolve, reject) => { reject(reason); }) } }
最後再說一個關於微任務的
setTimeout畢竟是個宏任務,咱們能夠用MutationObserver來模擬一個微任務,只要將下面的nextTick方法替換setTimeout方法便可
function nextTick(fn) { if (process !== undefined && typeof process.nextTick === "function") { return process.nextTick(fn); } else { // 實現瀏覽器上的nextTick var counter = 1; var observer = new MutationObserver(fn); var textNode = document.createTextNode(String(counter)); observer.observe(textNode, { characterData: true, }); counter += 1; textNode.data = String(counter); } }
這個方法的原理不難看懂,就是在dom裏建立了一個textNode,用MutationObserver監控這個node的變化。在執行nextTick方法的時候手動修改這個textNode,觸發MutationObserver的callback,這個callback就會在微任務隊列中執行。
注意MutationObserver的兼容性。
我我的感受完整理解Promise的源碼仍是比較考驗代碼功底的,一開始建議把源碼放在編譯器裏一點一點調試着看,若是實在不知道怎麼下手,也能夠把代碼背下來,慢慢咀嚼。實際上,背下來以後,人腦對這個東西會有一個緩慢的理解過程,到了某一天會感受恍然大悟。