手寫Promise原理
我的promise能實現什麼?
1:解決回撥地獄,實現非同步
2:可以鏈式呼叫,可以巢狀呼叫
3:有等待態到成功態的方法,有等待態到失敗態的方法
4:可以衍生出周邊的方法,如Promise.resolve(),Promise.reject(),Promise.prototype.then(),Promise.prototype.catch(),Promise.all() // 所有的完成
5. 可以根據自己的需求調節自己的promise
let PromiseA = require('./PromiseA'); const promiseA = new PromiseA((resolve, reject) => { resolve(new PromiseA((resolve,reject)=>{ setTimeout(()=>{ resolve(100) },1000) })) }) promiseA.then(data=>{ console.log(data) })
下面開始實現promise,首先創造三個常量,等待,成功,失敗
const PENDING = 'PENDING'; // 等待狀態 const RESOLVED = 'RESOLVED'; // 成功狀態 const REJECTED = 'REJECTED'; // 失敗狀態
然後創造一個promiseA類,裡面有constructor,then方法,catch方法。這裡的catch其實就是失敗的then,即then(null,(err)=>{...})
catch(err){ return this.then(null,err) }
class PromiseA { constructor(){...} then(){...}
}
我們重點關注constructor和then,先來看constructor。這裡promiseA預設的狀態是等待態,成功的值value預設為undefined,失敗的值reason預設為undefined。這裡的onResolvedCallbacks和onRejectedCallbacks是一個釋出訂閱的陣列,我們先不管。然後有resolve方法,reject方法
還有Promise自帶一個executor執行器,就是傳進來的引數。會立即執行 。 但有可能出錯。所以用try,catch包住。 executor裡有倆個引數,就是resolve和reject。就是promise傳進來引數的倆個方法。裡面具體的細節,我們往下看。。。
constructor(executor) { this.status = PENDING; // 預設等待狀態 this.value = undefined; this.reason = undefined; this.onResolvedCallbacks = []; this.onRejectedCallbacks = []; let resolve = (value) => { if(value instanceof PromiseA){ value.then(resolve,reject) return } if (this.status === PENDING) { this.value = value; this.status = RESOLVED; this.onResolvedCallbacks.forEach(fn => fn()); } } let reject = (reason) => { if (this.status === PENDING) { this.reason = reason; this.status = REJECTED; this.onRejectedCallbacks.forEach(fn => fn()); } } try { executor(resolve, reject); } catch (e) { reject(e) } }
然後我們在看看then,then裡面傳進來倆個引數,其實就是倆個方法。一個成功的回撥,一個失敗的回撥。我們重點看一下,三個狀態的執行,即status的走向。如果是resolve,即執行成功的回撥onFulfilled。如果是reject,即執行失敗的回撥onRejected。如果是等待,即執行一個釋出訂閱的模式,釋出訂閱,其實就是,我先將成功的回撥或者的失敗的回撥各自放入對應的陣列,即是上面我們跳過的倆個數組onResolvedCallbacks和onRejectedCallbacks 。然後,當狀態改變為resolve或者reject的時候,即遍歷執行對應的回撥函式。至此非同步就實現了,回撥地獄解決。這個非同步解決就是靠釋出訂閱模式來解決的。
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled:v=>v onRejected = typeof onRejected === 'function' ? onRejected:e=>{throw e} let promise2 = new PromiseA((resolve, reject) => { if (this.status === RESOLVED) { setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e) } }); } if (this.status === REJECTED) { setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e) } }); } if (this.status === PENDING) { this.onResolvedCallbacks.push(() => { setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(err); } }); }) this.onRejectedCallbacks.push(() => { setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(err); } }); }) } }) return promise2; }
接下來我們繼續實現鏈式呼叫,既是一個promiseA.then的結果返回的e是promise2的then的data。 一個成功的結果返回給下一個then作為引數。
let promise2 = promiseA.then(e=>{ return e } ) promise2.then(data=>{ console.log(data,'123') })
那麼繼續實現,還是上面的函式。這裡我們重點觀察這個promise2和setTimeout和resolvePromise. 我們先說promise2,這裡的promise2,其實是一個新的promise.也就是說promise的鏈式呼叫靠的就是返回一個新的promise.這裡把之前的三種狀態包起來,目的就是可以讓裡面得到的結果,獲取給promise2的resolve和reject。有人可能會說,那麼promise2的resolve或者reject要還是promise怎麼辦?這裡我們就要用到resolvePromise方法來判斷了。所以我們將成功的回撥函式resolve換成resolvePromise方法來執行。這裡我們要明白,resolve是執行成功的回撥函式。不管狀態是成功還是失敗,如果執行成功都是走向resolve。所以resolve和reject的狀態如果執行成功都是走向resolve。
let promise2 = new PromiseA((resolve, reject) => { if (this.status === RESOLVED) { setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e) } }); } if (this.status === REJECTED) { setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e) } }); } if (this.status === PENDING) { this.onResolvedCallbacks.push(() => { setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(err); } }); }) this.onRejectedCallbacks.push(() => { setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(err); } }); }) } }) return promise2;
然後我們在看resolvePromise方法,走到這裡,說明是執行成功的回撥函數了。傳進去的引數有promise2,x,promise2的resolve,promise2的reject。首先promise2是一個new promise。 這樣傳進去引數是會報錯的。因為執行到這一步,promise2還沒有生成。所以是會報錯的。
所以我們加一個setTimeout包住它,這樣就可以等promise2生成完在執行。這裡的setTimeout涉及到了事件迴圈,也就是巨集任務和微任務的部分。js執行機制是先執行主執行緒,然後執行微任務佇列,然後進行渲染,然後在執行巨集任務佇列。巨集任務執行完,如果巨集任務裡還包著js任務,就繼續迴圈反覆。直到所有任務執行完成。這裡的setTimeout是巨集任務,
resolvePromise(promise2, x, resolve, reject);
剛剛說到setTimeout,這裡貼上一段程式碼。這裡的newPromise是在setTimeout前執行的。
console.log(1); setTimeout(() => { console.log("我是定時器,延遲0S執行的"); }, 0); new Promise((resolve, reject) => { console.log("new Promise是同步任務裡面的巨集任務"); resolve("我是then裡面的引數,promise裡面的then方法是巨集任務裡面的微任務"); }).then(data => { console.log(data); }); console.log(2);
好的引數都傳進去了,接下來我們看resolvePromise的具體方法。就是一個判斷回撥函式x是不是promise,如果是就在迴圈拆開。直到不是為止。如果是普通值的話就可以直接返回了。至此,所以的promise庫就實現完了。至於後面的all和其他周邊方法就是語法糖了。主要核心部分掌握了,後面的周邊方法就不算什麼了。
function resolvePromise(promise2, x, resolve, reject) { if (promise2 === x) { return reject(new TypeError('返回的promise和當前promise不能是同一個物件哦,會嵌入死迴圈')) } if ((typeof x === 'object' && x !== null) || typeof x === 'function') { try { let then = x.then; if (typeof then === 'function') { then.call(x,y=> { resolvePromise(promise2, y, resolve, reject) },r=> { reject(r) } ) } else { resolve(x) } } catch (err) { reject(err) } } else { resolve(x); } }
至於resolve方法裡判斷,我們來看看。其實也是一個遞迴。判斷PromiseA裡的resolve裡面是不是promise,一直拆開。跟上面的方法類似。
let resolve = (value) => { if(value instanceof PromiseA){ value.then(resolve,reject) return } if (this.status === PENDING) { this.value = value; this.status = RESOLVED; this.onResolvedCallbacks.forEach(fn => fn()); } }
而至於這倆行程式碼,就是解決一個不斷向jquery那樣then的情況。如果是函式的話,將自己的引數作為結果返回。傳遞給下一個then。
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled:v=>v onRejected = typeof onRejected === 'function' ? onRejected:e=>{throw e}
promiseA.then().then().then().then().then()