手寫一款符合Promise/A+規範的Promise
手寫一款符合Promise/A+規範的Promise
長篇預警!有點長,可以選擇性觀看。如果對Promise原始碼不是很清楚,還是推薦從頭看,相信你認真從頭看到尾,並且去實際操作了,肯定會有收穫的。主要是程式碼部分有點多,不過好多都是重複的,不必擔心
Promise的一些用法在此不多贅述,本篇主要帶領你手寫一個Promise原始碼,學完你就會發現:Promise沒有你想象中的那麼難
本篇大概分為以下步驟
- 實現簡單的同步Promise
- 增加非同步功能
- 增加鏈式呼叫then
- 增加catch finally方法
- 增加all race 等方法
- 實現一個promise的延遲物件defer
- 最終測試
實現簡單的同步Promise
先大概說一下基本概念:
Promise內部維護著三種狀態,即pending,resolved和rejected。初始狀態是pending,狀態可以有pending--->relolved,或者pending--->rejected.不能從resolve轉換為rejected 或者從rejected轉換成resolved.
即 只要Promise由pending狀態轉換為其他狀態後,狀態就不可變更。
ok.知道了這些後,我們開始手擼程式碼:
注意觀看序號 1 2 3 4 5 ...
function Promise(executor){ let that = this; /** 2 定義初始的一些變數 */ that.status = 'pending'; that.value = null; that.reason = null; /** 3 定義初始的成功和失敗函式 */ function resolve(value){ /** 4 判斷狀態是不是初始狀態pending *是就轉換狀態 否則不轉換 *確保狀態的變化後的不可變性 */ if(that.status === 'pending'){ that.status = 'resolved'; that.value = value; } } function reject(reason){ /** 5 同上 */ if(that.status === 'pending'){ that.status = 'rejected'; that.reason = reason; } } /** * 1 Promise中首先傳了一個executor,它是一個函式 *executor函式中又傳了兩個函式,分別是resolve和reject *很顯然 resolve是成功回撥,reject是失敗的回撥 */ executor(resolve,reject); } /** 6 在Promise原型上面定義then方法 *then方法上面有兩個回撥 一個是成功後的方法 另一個是失敗後的方法 *根據成功或失敗的狀態去執行相關成功onFilfulled()或者失敗onRejected()的回撥方法 */ Promise.prototype.then = function(onFilfulled,onRejected){ let that = this; if(that.status === 'resolved'){ /** 7 如果狀態已經變更為resolved *說明resolve方法已經被呼叫 *那麼此時就執行成功的回撥函式onFilfulled *並傳入引數 that.value * */ onFilfulled(that.value); } if(that.status === 'rejected'){ /** 8 同上 *傳入引數 that.reason */ onRejected(that.reason); } } module.exports = Promise;
通過require()引入手擼的Promise
let Promise = require('./myPromise'); let p1 = ()=>{ return new Promise((resolve,reject)=>{ resolve('success.1'); }); } p1().then((data)=>{ console.log(data); // 列印 success.1 },(err)=>{ console.log(err); });
ok.經呼叫發現 此程式碼可以實現部分Promise的功能,但僅僅是同步下才有效果。
那非同步呢? 別急這就來~:
增加非同步功能
注意觀看序號 1 2 3 4 5 ...
function Promise(executor){ let that = this; that.status = 'pending'; that.value = null; that.reason = null; /** 1 因為非同步不是立即執行 狀態不會變更 成功或失敗的回撥函式也不會執行 *所以先定義好存放成功或失敗回撥函式的陣列 *以便將成功或失敗的回撥函式先儲存起來 * */ that.onFilFulledCallbacks = []; that.onRejectedCallbacks = []; function resolve(value){ if(that.status === 'pending'){ that.status = 'resolved'; that.value = value; /** 3 釋出 *等待狀態發生變更 *狀態變更後 立即執行之前存放在相應陣列中所有的成功或失敗的回撥函式 *即 釋出 */ that.onFilFulledCallbacks.forEach((fn)=>{ fn(); }); } } function reject(reason){ if(that.status === 'pending'){ that.status = 'rejected'; that.reason = reason; /** 4 同上 */ that.onRejectedCallbacks.forEach((fn)=>{ fn(); }); } } executor(resolve,reject); } Promise.prototype.then = function(onFilfulled,onRejected){ let that = this; if(that.status === 'resolved'){ onFilfulled(that.value); } if(that.status === 'rejected'){ onRejected(that.reason); } /** 2 訂閱 *因為是非同步 狀態當時並沒有立即變更 所以狀態還是pending *此時需要把成功或者失敗的回撥函式存放到對應的陣列中 *等待狀態變更時 再從陣列中拿出來去執行 *即 訂閱 **存放陣列時 為了執行時方便 需要把回撥函式的外層包裹一層空函式 */ if(that.status === 'pending'){ that.onFilFulledCallbacks.push(function(){ onFilfulled(that.value); }); } if(that.status === 'pending'){ that.onRejectedCallbacks.push(function(){ onRejected(that.reason); }); } } module.exports = Promise;
程式碼測試:
let Promise = require('./myPromise'); let p1 = ()=>{ return new Promise((resolve,reject)=>{ setTimeout(function(){ resolve('success.1'); // reject('fail.'); },1500); }); } p1().then((data)=>{ console.log(data); // success.1 },(err)=>{ console.log(err); });
可以看到 1.5s後 執行了resolve() 並列印了success.1,至此,我們實現了非同步的Promise.其實這裡的實現非同步的思想就是釋出訂閱.
en~ok.高能預警:tiger:.接下來就稍稍複雜了 因為我們要實現鏈式呼叫then。 要實現這個功能那我們就要重寫then方法,並在then方法中重新返回一個Promise,只有這樣,才可以實現多次呼叫then.而且要新增一個解析返回值是否為promise的函式.
稍微捋下邏輯:
- 如果一個then方法返回一個普通值的話,這個值會傳遞給下一個then中作為resolve成功的結果
- 如果一個then方法返回一個promise的話,會根據返回的promise是成功還是失敗,決定下一個then是成功還是失敗
:ok_hand: 上程式碼:
增加鏈式呼叫then
注意觀看序號 1 2 3 4 5 ...
function Promise(executor){ let that = this; that.status = 'pending'; that.value = null; that.reason = null; that.onFilFulledCallbacks = []; that.onRejectedCallbacks = []; function resolve(value){ if(that.status === 'pending'){ that.status = 'resolved'; that.value = value; that.onFilFulledCallbacks.forEach((fn)=>{ fn(); }); } } function reject(reason){ if(that.status === 'pending'){ that.status = 'rejected'; that.reason = reason; that.onRejectedCallbacks.forEach((fn)=>{ fn(); }); } } executor(resolve,reject); } Promise.prototype.then = function(onFilfulled,onRejected){ let that = this; /** 1 讓promise2等於一個新的Promise 並將promise2返回 */ let promise2 = new Promise((resolve,reject)=>{ if(that.status === 'resolved'){ /** 2 因為返回了promise2 *並且第3步resolvePromiseRelation函式中傳遞了promise2 *而目前promise2並沒有拿到 *所以加一個定時器 非同步執行 等到promise2拿到後 *再去執行 resolvePromiseRelation()方法 並將promise2傳遞進去*/ setTimeout(()=>{ try{ let promise3 = onFilfulled(that.value); /** 3 判斷新返回值是什麼型別的函式 *並將當前的promise:promise2新的返回值:promise3 *和 成功時回撥:esolve失敗時回撥:reject 作為引數傳進去 */ resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); } if(that.status === 'rejected'){ /** 同2 */ setTimeout(()=>{ try{ let promise3 = onRejected(that.reason); /** 同3*/ resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); } if(that.status === 'pending'){ that.onFilFulledCallbacks.push(function(){ /** 同2 */ setTimeout(()=>{ try{ let promise3 = onFilfulled(that.value); /** 同3*/ resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); }); } if(that.status === 'pending'){ that.onRejectedCallbacks.push(function(){ /** 同2 */ setTimeout(()=>{ try{ let promise3 = onRejected(that.reason); /** 同3*/ resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); }); } }); /** 同1 */ return promise2; } function resolvePromiseRelation(promise2,promise3,resolve,reject){ /** 4 防止自己等待自己 一直迴圈等待 */ if(promise2 === promise3){ return reject(new TypeError('迴圈引用了!')); } /**8 一個標示 表示當前沒有被呼叫過 *確保resolve或者reject後的狀態不會再次發生變更 */ let called; /** 5 保證promise3是一個引用型別 *判斷新返回值promise3的型別 *如果是普通值常量 就直接resolve匯出 */ if(promise3!==null&&(typeof promise3 === 'object'||typeof promise3 === 'function')){ try{ /** 6 確保promise3是一個Promise *判斷promise3的then方法 *如果存在 並且是一個function型別 *就表示promise3是一個Promise */ if(typeof promise3.then === 'function'){ /** 9 執行promise3的then方法 *因為promise3也是一個Promise *需要再次解析promise3的then方法 *直到解析到最後的返回值不是一個Promise型別為止 */ promise3.then(promise3, (promise4)=>{ /** 同8 */ if(called) return; called = true; /** 10 遞迴解析新的返回值的型別 *解析到返回值不是一個Promise型別為止 */ resolvePromiseRelation(promise3,promise4,resolve,reject); },(r)=>{ /** 同8 */ if(called) return; called = true; reject(r); }); }else{ /** 7 此時promise3是一個普通物件 直接resolve() */ resolve(promise3); } }catch(e){ /** 同8 */ if(called) return; called = true; reject(e); }; }else{ /** 同5 普通值直接resolve()*/ resolve(promise3); } } module.exports = Promise;
ok. 至此 我們已經實現了Promsie的非同步和鏈式呼叫. Promise中比較複雜的部分我們已經搞定了 接下來就是新增一些方法,其實這部分反而沒那麼複雜了.
catch : catch方法本質上就是一個then方法的變形,只有失敗時的回撥 沒有成功時的回撥
finally : finally方法的作用是不管 Promise 物件最後狀態如何,都會執行操作.其實說白了就是在then方法的成功和失敗的回撥函式中都執行該方法就行了.
ok.老規矩 上程式碼~
增加catch finally方法
function Promise(executor){ let that = this; that.status = 'pending'; that.value = null; that.reason = null; that.onFilFulledCallbacks = []; that.onRejectedCallbacks = []; function resolve(value){ if(that.status === 'pending'){ that.status = 'resolved'; that.value = value; that.onFilFulledCallbacks.forEach((fn)=>{ fn(); }); } } function reject(reason){ if(that.status === 'pending'){ that.status = 'rejected'; that.reason = reason; that.onRejectedCallbacks.forEach((fn)=>{ fn(); }); } } executor(resolve,reject); } Promise.prototype.then = function(onFilfulled,onRejected){ /** 2 此處有個坑 如果只寫1 不寫2的話 *會報一個TypeError :onRejected is not a function *在此處給它一個預設的成功和失敗的回撥函式就好 */ onFilfulled = typeof onFilfulled === 'function'?onFilfulled:value=>value; onRejected = typeof onRejected ==='function'?onRejected:err=>{throw err}; let that = this; let promise2 = new Promise((resolve,reject)=>{ if(that.status === 'resolved'){ setTimeout(()=>{ try{ let promise3 = onFilfulled(that.value); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); } if(that.status === 'rejected'){ setTimeout(()=>{ try{ let promise3 = onRejected(that.reason); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); } if(that.status === 'pending'){ that.onFilFulledCallbacks.push(function(){ setTimeout(()=>{ try{ let promise3 = onFilfulled(that.value); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); }); } if(that.status === 'pending'){ that.onRejectedCallbacks.push(function(){ setTimeout(()=>{ try{ let promise3 = onRejected(that.reason); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); }); } }); return promise2; } function resolvePromiseRelation(promise2,promise3,resolve,reject){ if(promise2 === promise3){ return reject(new TypeError('迴圈引用了!')); } let called; if(promise3!==null&&(typeof promise3 === 'object'||typeof promise3 === 'function')){ try{ if(typeof promise3.then === 'function'){ promise3.then(promise3, (promise4)=>{ if(called) return; called = true; resolvePromiseRelation(promise3,promise4,resolve,reject); },(r)=>{ if(called) return; called = true; reject(r); }); }else{ resolve(promise3); } }catch(e){ if(called) return; called = true; reject(e); }; }else{ resolve(promise3); } } /** 1 直接返回this的then方法 *因為catch只捕獲錯誤 所以resolve直接為null *返回reject就好*/ Promise.prototype.catch = function(errFn){ return this.then(null,errFn); } /** 3 finally實現起來也很簡單 *分別在resolve和reject中執行fn就好 *最後再把this返回出去就好 */ Promise.prototype.finally = function(fn){ this.then(()=>{ fn(); },()=>{ fn(); }); return this; } module.exports = Promise;
增加all race 等方法
function Promise(executor){ let that = this; that.status = 'pending'; that.value = null; that.reason = null; that.onFilFulledCallbacks = []; that.onRejectedCallbacks = []; function resolve(value){ if(that.status === 'pending'){ that.status = 'resolved'; that.value = value; that.onFilFulledCallbacks.forEach((fn)=>{ fn(); }); } } function reject(reason){ if(that.status === 'pending'){ that.status = 'rejected'; that.reason = reason; that.onRejectedCallbacks.forEach((fn)=>{ fn(); }); } } executor(resolve,reject); } Promise.prototype.then = function(onFilfulled,onRejected){ onFilfulled = typeof onFilfulled === 'function'?onFilfulled:value=>value; onRejected = typeof onRejected ==='function'?onRejected:err=>{throw err}; let that = this; let promise2 = new Promise((resolve,reject)=>{ if(that.status === 'resolved'){ setTimeout(()=>{ try{ let promise3 = onFilfulled(that.value); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); } if(that.status === 'rejected'){ setTimeout(()=>{ try{ let promise3 = onRejected(that.reason); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); } if(that.status === 'pending'){ that.onFilFulledCallbacks.push(function(){ setTimeout(()=>{ try{ let promise3 = onFilfulled(that.value); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); }); } if(that.status === 'pending'){ that.onRejectedCallbacks.push(function(){ setTimeout(()=>{ try{ let promise3 = onRejected(that.reason); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); }); } }); return promise2; } function resolvePromiseRelation(promise2,promise3,resolve,reject){ if(promise2 === promise3){ return reject(new TypeError('迴圈引用了!')); } let called; if(promise3!==null&&(typeof promise3 === 'object'||typeof promise3 === 'function')){ try{ if(typeof promise3.then === 'function'){ promise3.then(promise3, (promise4)=>{ if(called) return; called = true; resolvePromiseRelation(promise3,promise4,resolve,reject); },(r)=>{ if(called) return; called = true; reject(r); }); }else{ resolve(promise3); } }catch(e){ if(called) return; called = true; reject(e); }; }else{ resolve(promise3); } } Promise.prototype.catch = function(errFn){ return this.then(null,errFn); } Promise.prototype.finally = function(fn){ this.then(()=>{ fn(); },()=>{ fn(); }); return this; } /** 1 直接在建構函式上增加all方法 *它返回的也是一個Promise *等待引數陣列中所有的promise都執行完畢後 *再返回結果 */ Promise.all = function(values){ return new Promise((resolve,reject)=>{ /** 2 定義一個存放最終結果的陣列result和一個index */ let results = []; let index = 0; /** 3 定義一個方法addToArr() *讓index每次執行增加results陣列元素的函式的時候都+1 *當index === values的長度的時候 說明此時所有promsie都執行完畢並放到的陣列中 *然後直接resolve(results)就行了 */ function addToArr(key,value){ index++; results[key] = value; /** 6 當滿足條件時 說明所有的promise都執行完畢 直接resolve(results) */ if(index === values.length){ resolve(results); } } /** 4 迴圈values中的每一項promsie */ for(let i = 0; i < values.length; i++){ let current = values[i]; /** 5 判斷每一項promise的返回值是不是一個Promsie *是的話就執行該Promise的then方法 拿到返回值 並放到陣列results中 *是一個普通值的話就直接將該值放到陣列results中 */ if(current && current.then && typeof current.then === 'function'){ current.then((value)=>{ /** 同5 把返回值放到陣列results中*/ addToArr(i,value); },reject); }else{ /** 同5 把返回值放到陣列results中*/ addToArr(i,current); } } }); } /** race方法相比較於all方法簡單很多 *因為race中的promsie成功resolve一個 *整個race就resolve */ Promise.race = function(values){ return new Promise((resolve,reject)=>{ /** 同4 */ for(let i = 0; i < values.length; i++){ let current = values[i]; /** 同5 */ if(current&¤t.then&&typeof current.then === 'function'){ /** 7 直接執行then就好 */ current.then(resolve,reject); }else{ /** 8 普通值直接resolve */ resolve(current); } } }); } module.exports = Promise;
實現一個promise的延遲物件defer
此步是為了測試我們手寫的Promsie符不符合Promsie/A+規範,如果沒有defer的話,我們在測試過程中就會報一個TypeError: adapter.deferred is not a function.
其實寫完defer後,我們就可以去進行測試我們手寫的Promsie符不符合Promsie/A+規範了。
即:本篇手寫一款符合Promise/A+規範的Promise的最終本為:
function Promise(executor){ let that = this; that.status = 'pending'; that.value = null; that.reason = null; that.onFilFulledCallbacks = []; that.onRejectedCallbacks = []; function resolve(value){ if(that.status === 'pending'){ that.status = 'resolved'; that.value = value; that.onFilFulledCallbacks.forEach((fn)=>{ fn(); }); } } function reject(reason){ if(that.status === 'pending'){ that.status = 'rejected'; that.reason = reason; that.onRejectedCallbacks.forEach((fn)=>{ fn(); }); } } executor(resolve,reject); } Promise.prototype.then = function(onFilfulled,onRejected){ onFilfulled = typeof onFilfulled === 'function'?onFilfulled:value=>value; onRejected = typeof onRejected === 'function'?onRejected:err=>{throw err}; let that = this; let promise2 = new Promise((resolve,reject)=>{ if(that.status === 'resolved'){ setTimeout(()=>{ try{ let promise3 = onFilfulled(that.value); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); } if(that.status === 'rejected'){ setTimeout(()=>{ try{ let promise3 = onRejected(that.reason); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); } if(that.status === 'pending'){ that.onFilFulledCallbacks.push(function(){ setTimeout(()=>{ try{ let promise3 = onFilfulled(that.value); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); }); that.onRejectedCallbacks.push(function(){ setTimeout(()=>{ try{ let promise3 = onRejected(that.reason); resolvePromiseRelation(promise2,promise3,resolve,reject); }catch(e){ reject(e); } },0); }); } }); return promise2; } function resolvePromiseRelation(promise2,promise3,resolve,reject){ if(promise2 == promise3){ return reject(new TypeError('迴圈引用了!')); } let called; if(promise3!==null&&(typeof promise3 === 'object' || typeof promise3 === 'function')){ try{ if(typeof promise3.then === 'function'){ promise3.then.call(promise3, (promise4)=>{ if(called) return; called = true; resolvePromiseRelation(promise3,promise4,resolve,reject); },(r)=>{ if(called) return; called = true; reject(r); }); }else{ resolve(promise3); } }catch(e){ if(called) return; called = true; reject(e); }; }else{ resolve(promise3); } } Promise.prototype.catch = function(errFn){ return this.then(null,errFn); } Promise.prototype.finally = function(fn){ this.then(()=>{ fn(); },()=>{ fn(); }); return this; } Promise.all = function(values){ return new Promise((resolve,reject)=>{ let results = []; let index = 0; function addToArr(key,value){ index++; results[key] = value; if(index === values.length){ resolve(results); } } for(let i = 0; i < values.length; i++){ let current = values[i]; if(current && current.then && typeof current.then === 'function'){ current.then((value)=>{ addToArr(i,value); },reject); }else{ addToArr(i,current); } } }); } Promise.race = function(values){ return new Promise((resolve,reject)=>{ for(let i = 0; i < values.length; i++){ let current = values[i]; if(current&¤t.then&&typeof current.then === 'function'){ current.then(resolve,reject); }else{ resolve(current); } } }); } // 實現一個promise的延遲物件 defer Promise.defer = Promise.deferred = function(){ let dfd = {}; dfd.promise = new Promise((resolve, reject)=>{ dfd.resolve = resolve; dfd.reject = reject; }); return dfd; } module.exports = Promise;
最終測試
- 測試當前程式碼是否符合Promise/A+規範
- 全域性安裝 npm i -g promises-aplus-tests
- 檔案所在目錄執行以下命令 (例如你的檔名為:MyPrommise.js)
- promise-aplus-tests MyPrommise.js
- 等待
-
ok.
原始碼在github上,寫文章不易,歡迎star或fork,thx~
github