我始終認為要想真正的理解一個API也好,內置對象也罷,最好的方式就是手寫一遍。
手寫Promise
const PENDING = "pending"; const RESOLVED = "resolved"; const REJECTED = "rejected"; function MyPromise() { //保存初始化狀態 let selt = this; //初始化狀態 this.state = PENDING; //用於保存resolve或者rejected傳入的值 this.value = null; //用於保存resolve的回調函數 this.resolvedCallbacks = []; //用於保存reject的回調函數 this.rejectedCallbacks = []; //狀態轉變為resolved的方法 function resolve(value) { //判斷傳入的元素是否為Promise值,如果是,則狀態改變必須等待前一個狀態改變後再進行改變 if (value instanceof MyPromise) { return value.then(resolve, reject); } //保證代碼的執行順序為本輪事件循環的末尾 setTimeout(() => { //隻有狀態為pending時才能轉變 if (self.state === PENDING) { //修改狀態 self.state = RESOLVED; //設置傳入的值 self.value = value; //執行回調函數 self.resolvedCallbacks.forEach((callback) => { callback(value); }); } }, 0); } //狀態轉變為rejected方法 function reject(value) { //保證代碼的執行順序為本輪事件循環的末尾 setTimeout(() => { //隻有狀態為pending時才能轉變 if (self.state === PENDING) { //修改狀態 self.state = REJECTED; //設置傳入的值 self.value = value; //執行回調函數 self.rejectedCallbacks.forEach((callback) => { callback(value); }); } }, 0); } //將兩個方法傳入函數執行 try { fn(resolve, reject); } catch (e) { //遇到錯誤時,捕獲錯誤,執行reject函數 reject(e); } MyPromise.prototype.then = function (onResolved, onRejected) { //首先判斷兩個參數是否為函數類型,因為這兩個參數是可選參數 onResolved = typeof onResolved === "function" ? onResolved : function (value) { return value; }; onRejected = typeof onRejected === "function" ? onRejected : function (error) { throw error; }; //如果是等待狀態,則將函數加入對應列表中 if (this.state === PENDING) { this.resolvedCallbacks.push(onResolved); this.rejectedCallbacks.push(onRejected); } //如果狀態已經凝固,則直接執行對應狀態的函數 if (this.state === RESOLVED) { onResolved(this.value); } if (this.state === REJECTED) { onRejected(this.value); } }; }
手寫Promise.then
then
方法返回一個新的Promise
實例,為瞭在Promise
狀態發生變化時再執行then
裡的函數,我們使用一個callbacks
數組先把傳給then
的函數暫存起來,等狀態改變時再調用
那麼,怎麼保證後一個then裡的方法在前一個then(可能是異步)結束之後再執行呢?
我們可以將傳給then
函數和新Promise
的resolve
一起push
到前一個Promise
的callbacks
數組中,達到承前啟後的效果:
- 承前:當前一個
Promise
完成後,調用其resolve
變更狀態,在這個resolve裡會依次調用callbacks
裡的回調,這樣就執行瞭then
裡的方法 - 啟後:上一步中,當
then
裡的方法執行完成後,返回一個結果,如果這個結果是個簡單的值,就直接調用新Promise
的resolve
,讓其狀態變更,這又會依次調用新Promise
的callbacks
數組裡的方法,循環往復。如果返回的結果是個Promise
,則需要等它完成之後再出發新Promise
的resolve
,所以可在其結果的then
裡調用新Promise
的resolve
then(onFulfilled, onReject) { //保存前一個Promise的this const self = this; return new MyPromise((resolve, reject) => { //封裝前一個Promise成功時執行的函數 let fulfilled = () => { try { const result = onFulfilled(self.value); //承前 return result instanceof MyPromise ? result.then(resolve, reject) : resolve(result); //啟後 } catch (e) { reject(e); } }; //封裝前一個Promise失敗時執行的函數 let rejected = () => { try { const result = onRejected(self.reason); return result instanceof MyPromise ? result.then(resolve, reject) : reject(result); } catch (e) { reject(e); } }; switch (self.status) { case PENDING: self.onFulfilledCallbacks.push(fulfilled); self.onRejectedCallbacks.push(rejected); break; case FULFILLED: fulfilled(); break; case REJECT: rejected(); break; } }); };
註意:
- 連續多個
then
裡的回調方法是同步註冊的,但註冊到瞭不同的callbacks
數組中,因為每次then
都返回新的Promise
實例 - 註冊完成後開始執行構造函數中的異步事件,異步完成之後依次調用
callbacks
數組中提前註冊的回調
手寫Promise.all
- 接收一個Promise實例的數組或具有Iterator接口的對象作為參數
- 這個方法返回一個新的Promise對象
- 遍歷傳入的參數,用Promise.resolve()將參數“包一層”,使其變成一個Promise對象
- 參數所有回調成功才是成功,返回值數組與參數順序一致
- 參數數組其中一個失敗,則觸發失敗狀態,第一個觸發失敗狀態的Promise錯誤信息作為Promise.all的錯誤信息
function promiseAll(promises){ return new Promise(function(resolve, reject){ if(!Array.isArray(promises)){ throw new TypeError(`argument must be a array`) } let resolvedCounter =0;//已resolve的promise數 let promiseNum=promises.length; let resolvedResult =[];//暫存resolve結果的數組 for(let i=0;i<promiseNum;i++){ Promise.resolve(promises[i]).then(value=>{ resolvedCounter++; resolvedResult[i]=value; if(resolvedCounter==promiseNum){ return resolve(resolvedResult) } },error=>{ return reject(error) }) } }) }//test let p1=new Promise(function(resolve, reject){ setTimeout(function(){ resolve(1) },1000) }); let p2=new Promise(function(resolve, reject){ setTimeout(function(){ resolve(2) },2000) }); let p3=new Promise(function(resolve, reject){ setTimeout(function(){ resolve(3) },3000) }); promiseAll([p3,p1,p2]).then(res=>{ console.log(res);//[3,1,2] })
手寫Promise.race
該方法的參數是Promise實例數組,然後其then註冊的回調方法是數組中的某一個Promise的狀態變為fufilled的時候執行。因為Promise的狀態隻能改變一次,那麼我們隻需要把Promise.race中產生的Promise對象的resolve,註入到數組中的每一個Promise實例中的回調函數即可。
Promise.race=function (args){ return new Promise((resolve,reject)=>{ for(let i=0,len=args.length;i<len;i++){ args[i].then(resolve, reject) } }) }
手寫Promise.finally
finally (onFinally) { return new Promise((resolve, reject) => { this.then((result) => { onFinally(); resolve(result); }, (reason) => { onFinally(); reject(reason); }); }); }