摘要:本文同時也發布在我的博客上,歡迎之前也手寫過簡單的,這次則是為了通過官方的測試集,借鑒了一些下載量較多的,改了幾遍,終于是通過了規范的個測試用例如何測試測試庫地址在這,大家在寫完自己的后,不妨也去測試一下,檢驗自己的是否符合規范。
本文同時也發布在我的github博客上,歡迎star~
之前也手寫過簡單的promise,這次則是為了通過官方的Promise A+測試集,借鑒了一些下載量較多的promise polyfill,改了幾遍,終于是通過了A+規范的872個測試用例
如何測試?測試庫地址在這:promises-tests ,大家在寫完自己的promise后,不妨也去測試一下,檢驗自己的promise是否符合Promise A+規范。這個庫使用起來很方便,像下面這樣就可以了:
const tests = require("promises-aplus-tests"); const Promise = require("./index"); const deferred = function() { let resolve, reject; const promise = new Promise(function(_resolve, _reject) { resolve = _resolve; reject = _reject; }); return { promise: promise, resolve: resolve, reject: reject }; }; const adapter = { deferred }; tests.mocha(adapter);
其中,index.js中是你寫的Promise
實現首先我們定義一些全局屬性:
const IS_ERROR = {}; let ERROR = null;
IS_ERROR作為發生錯誤時的標識,ERROR用來保存錯誤;
做好準備工作,再來定義_Promise類,其中fn是Promise接受的函數,構造函數執行時立刻調用;_status是Promise的狀態,初始為0(pending),resolved時為1,rejected時為2;_value用來保存Promise resolved時的返回值和rejected時的失敗信息;_handlers用來保存Promise成功和失敗時調用的處理方法
function _Promise(fn) { this._status = 0; this._value = null; this._handlers = []; doFn(this, fn); }
最后執行doFn方法,傳入this值和fn:
function doFn(self, fn) { const ret = safeCallTwo( fn, function(value) { self.resolve(value); }, function(reason) { self.reject(reason); } ); if (ret === IS_ERROR) { self.reject(ERROR); } }
其中safeCallTwo是用來安全執行兩參數方法的函數,當執行出錯時,捕獲錯誤,保存在ERROR中,返回IS_ERROR標識:
function safeCallTwo(fn, arg1, arg2) { try { return fn(arg1, arg2); } catch (error) { ERROR = error; return IS_ERROR; } }
在doFn中,調用safeCallTwo,fn傳入兩個參數供我們調用,也就是我們常用的resolve方法和reject方法,并獲取到返回值,如果ret為錯誤標識IS_ERROR,則調用reject
_Promise原型上掛載著resolve和reject方法,如下:
_Promise.prototype.resolve = function(value) { if (this._status !== 0) { return; } this._status = 1; this._value = value; doThen(this); }; _Promise.prototype.reject = function(reason) { if (this._status !== 0) { return; } this._status = 2; this._value = reason; doThen(this); };
因為Promise的狀態只能由pending轉為resolved和rejected,所以在執行resolve和reject方法時,要先判斷status是否為0,若不為0,直接return;修改status和value后,執行doThen方法:
function doThen(self) { const handlers = self._handlers; handlers.forEach(handler => { doHandler(self, handler); }); }
doThen函數的作用是從self上取出的handlers并依次執行
我們再來看一看掛載在原型上的then方法:
_Promise.prototype.then = function(onResolve, onReject) { const res = new _Promise(function() {}); preThen(this, onResolve, onReject, res); return res; };
我們知道,Promise是支持鏈式調用的,所以我們的then方法也會返回一個Promise,以供后續調用;
下面是preThen方法:
function preThen(self, onResolve, onReject, res) { onResolve = typeof onResolve === "function" ? onResolve : null; onReject = typeof onReject === "function" ? onReject : null; const handler = { onResolve, onReject, promise: res }; if (self._status === 0) { self._handlers.push(handler); return; } doHandler(self, handler); }
preThen方法接受4個值,分別為當前Promise——self,resolve后的回調函數onResolve,reject后的回調函數onReject,then函數返回的promise——res。先判斷onResolve和onReject是否為函數,若不是,直接置為null。再將onResolve、onReject、res放入handler對象中
接下來需要注意,Promise接受的函數(也就是上文的fn)并不是一定是異步調用resolve和reject,也有可能是同步的,也就是說在執行preThen函數時,self的status可能已經不為0了,這時候我們就不需要將handler保存起來等待調用,而是直接調用回調函數
doHandler函數代碼見下:
function doHandler(self, handler) { setTimeout(() => { const { onReject, onResolve, promise } = handler; const { _status, _value } = self; const handlerFun = _status === 1 ? onResolve : onReject; if (handlerFun === null) { _status === 1 ? promise.resolve(_value) : promise.reject(_value); return; } const ret = safeCallOne(handlerFun, _value); if (ret === IS_ERROR) { promise.reject(ERROR); return; } promise.resolve(ret); }); }
我們知道,即使是同步執行relove或者reject,then函數接受的回調函數也不會立刻同步執行,如下代碼會依次輸出1,3,2,而非1,2,3
const p = new Promise(resolve => { console.log(1); resolve(); }); p.then(() => { console.log(2); }); console.log(3);
在這里,我使用了setTimeout來模擬這種模式,當然,這只是一種粗糙的模擬,更好的方式是引入或實現類似asap的庫(下個星期我可能會實現這個,哈哈),但setTimeout也足夠通過測試了
doHandler函數中,我們調用相應的回調函數,需要注意的是,如果相應回調函數為null(null是前文判斷回調函數不為function時統一賦值的),則直接調用then函數返回的promise的resolve或reject方法。
同樣,我們使用了safeCallOne來捕獲錯誤,這里不再贅述
到這里,我們執行測試,發現不出意外地沒有通過,因為我們只是實現了基礎的Promise,還沒有實現resolve中的thenable功能,下面是mdn對于thenable的描述:
返回一個狀態由給定value決定的Promise對象。如果該值是thenable(即,帶有then方法的對象),返回的Promise對象的最終狀態由then方法執行決定;否則的話(該value為空,基本類型或者不帶then方法的對象),返回的Promise對象狀態為fulfilled,并且將該value傳遞給對應的then方法。通常而言,如果你不知道一個值是否是Promise對象,使用Promise.resolve(value) 來返回一個Promise對象,這樣就能將該value以Promise對象形式使用
我們再來修改resolve方法:
_Promise.prototype.resolve = function(value) { if (this._status !== 0) { return; } if (this === value) { return this.reject(new TypeError("cant"s resolve itself")); } if (value && (typeof value === "function" || typeof value === "object")) { const then = getThen(value); if (then === IS_ERROR) { this.reject(ERROR); return; } if (value instanceof _Promise) { value.then( value => { this.resolve(value); }, reason => { this.reject(reason); } ); return; } if (typeof then === "function") { doFn(this, then.bind(value)); return; } } this._status = 1; this._value = value; doThen(this); };
先判斷this和value是否為一個Promise,若是一個,則拋出錯誤
再判斷value的類型是否為function或object,如果是,則實行getThen方法進行錯誤捕獲:
function getThen(self) { try { return self.then; } catch (error) { ERROR = error; return IS_ERROR; } }
若成功拿到then方法,檢測value instanceof _Promise,若為true,則直接采用value的狀態和value或者reason。
若then為function,則將then函數以value為this值,當作fn執行,也就是達成下面代碼的效果:
const p = new Promise(resolve => { resolve({ then: _resolve => { _resolve(1); } }); }); p.then(value => console.log(value)); //打印1
我們再次執行測試,發現仍然有錯,其因出現在下面這種情況下:
const p = new _Promise(resolve => { resolve({ then: _resolve => { setTimeout(() => _resolve(1)), 500; } }); resolve(2); }); p.then(value => console.log(value));
這個時候,使用我們的Promise,輸出的是2,而在規范中,應當是輸出1
原因是我們在對象的then方法中是異步地resolve,這個時候,下面的resolve(2)在執行時,status還沒有變,自然可以修改status和value
解決方法也很簡單,只用在doFn方法中判斷是否為第一次執行即可:
function doFn(self, fn) { let done = false; const ret = safeCallTwo( fn, function(value) { if (done) { return; } done = true; self.resolve(value); }, function(reason) { if (done) { return; } done = true; self.reject(reason); } ); if (ret === IS_ERROR) { if (done) { return; } done = true; self.reject(ERROR); } }
再執行測試,發現已經測試用例全部通過~
完整代碼已放在我的github上,地址為https://github.com/Bowen7/playground/tree/master/promise-polyfill ,可以clone我的playground項目,再到promise-polyfill目錄下npm install,然后執行npm test即可運行測試
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/106646.html
摘要:手寫一款符合規范的長篇預警有點長,可以選擇性觀看。初始狀態是,狀態可以有或者不能從轉換為或者從轉換成即只要由狀態轉換為其他狀態后,狀態就不可變更。 手寫一款符合Promise/A+規范的Promise 長篇預警!有點長,可以選擇性觀看。如果對Promise源碼不是很清楚,還是推薦從頭看,相信你認真從頭看到尾,并且去實際操作了,肯定會有收獲的。主要是代碼部分有點多,不過好多都是重復的,不...
摘要:傳入的回調函數也不是一個函數類型,那怎么辦規范中說忽略它就好了。因此需要判斷一下回調函數的類型,如果明確是個函數再執行它。 Promise是什么 所謂Promise,簡單說就是一個容器,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果。從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。Promise 提供統一的 API,各種異步操作都可以用同樣的方法進行處...
摘要:如果實現滿足所有要求,則實現可能允許。本條款允許使用特定于實現的方法來采用已知一致承諾的狀態。接下來根據規范進行手寫實現注釋偷懶就將對應的規范標注出來,其實基本上就是對著規范實現。 如果要手寫實現promise,那么先看看promise/A+規范,再來實現,將會事半功倍。那么我先翻譯一下Promise/A+規范中的內容。 術語 1.1 promise 是一個帶有符合此規范的the...
摘要:使用及原理分析通過關鍵字創建實例接受一個參數方法返回兩個方法可用通過在方法中通過調用使成功或調用使失敗來控制狀態中可以執行同步代碼也可以執行異步代碼原型對象上有方法供實例調用方法接受兩個參數默認為一個函數默認為一個函數當狀態為時執行用戶傳入 promise使用及原理分析: 通過new關鍵字創建promise實例, 接受一個executor參數, executor方法返回兩個方法 res...
摘要:如果狀態是等待態的話,就往回調函數中函數,比如如下代碼就會進入等待態的邏輯以上就是簡單版實現實現一個符合規范的接下來大部分代碼都是根據規范去實現的。 為更好的理解, 推薦閱讀Promise/A+ 規范 實現一個簡易版 Promise 在完成符合 Promise/A+ 規范的代碼之前,我們可以先來實現一個簡易版 Promise,因為在面試中,如果你能實現出一個簡易版的 Promise ...
閱讀 2597·2021-10-14 09:43
閱讀 3559·2021-10-13 09:39
閱讀 3289·2019-08-30 15:44
閱讀 3137·2019-08-29 16:37
閱讀 3702·2019-08-29 13:17
閱讀 2731·2019-08-26 13:57
閱讀 1825·2019-08-26 11:59
閱讀 1238·2019-08-26 11:46