在個人上一篇文章裏着重介紹了async的相關知識,對promise的說起甚少,如今不少面試也都要求咱們有手動造輪子的能力,因此本篇文章我會以手動實現一個promise的方式來發掘一下Promise的特色.面試
首先咱們應該知道Promise是經過構造函數的方式來建立的(new Promise( executor )),而且爲 executor函數 傳遞參數:promise
function Promi(executor) { executor(resolve, reject); function resolve() {} function reject() {} }
再來講一下Promise的三種狀態: pending-等待, resolve-成功, reject-失敗, 其中最開始爲pending狀態, 而且一旦成功或者失敗, Promise的狀態便不會再改變,因此根據這點:async
function Promi(executor) { let _this = this; _this.$$status = 'pending'; executor(resolve.bind(this), reject.bind(this)); function resolve() { if (_this.$$status === 'pending') { _this.$$status = 'full' } } function reject() { if (_this.$$status === 'pending') { _this.$$status = 'fail' } } }
其中$$status來記錄Promise的狀態,只有當promise的狀態未pending時咱們纔會改變它的狀態爲'full'或者'fail', 由於咱們在兩個status函數中使用了this,顯然使用的是Promise的一些屬性,因此咱們要綁定resolve與reject中的this爲當前建立的Promise;
這樣咱們最最最基礎的Promise就完成了(只有頭部沒有四肢...)函數
接着,全部的Promise實例均可以用.then方法,其中.then的兩個參數,成功的回調和失敗的回調也就是咱們所說的resolve和reject:測試
function Promi(executor) { let _this = this; _this.$$status = 'pending'; _this.failCallBack = undefined; _this.successCallback = undefined; _this.error = undefined; executor(resolve.bind(_this), reject.bind(_this)); function resolve(params) { if (_this.$$status === 'pending') { _this.$$status = 'success' _this.successCallback(params) } } function reject(params) { if (_this.$$status === 'pending') { _this.$$status = 'fail' _this.failCallBack(params) } } } Promi.prototype.then = function(full, fail) { this.successCallback = full this.failCallBack = fail }; // 測試代碼 new Promi(function(res, rej) { setTimeout(_ => res('成功'), 30) }).then(res => console.log(res))
講一下這裏:
能夠看到咱們增長了failCallBack和successCallback,用來儲存咱們在then中回調,剛纔也說過,then中可傳遞一個成功和一個失敗的回調,當P的狀態變爲resolve時執行成功回調,當P的狀態變爲reject或者出錯時則執行失敗的回調,可是具體執行結果的控制權沒有在這裏。可是咱們知道必定會調用其中的一個。this
executor任務成功了確定有成功後的結果,失敗了咱們確定也拿到失敗的緣由。因此咱們能夠經過params來傳遞這個結果或者error reason(固然這裏的params也能夠拆開賦給Promise實例)其實寫到這裏若是是面試題,基本上是經過了,也不會有人讓你去完整地去實現spa
error:用來存儲,傳遞reject信息以及錯誤信息prototype
我想咱們最迷戀的應該就是Promise的鏈式調用吧,由於它的出現最最最大的意義就是使咱們的callback看起來不那麼hell(由於我以前講到了async比它更直接),那麼爲何then能鏈式調用呢? then必定返回了一個也具備then方法的對象
我想你們應該都能猜到.then返回的也必定是一個promise,那麼這裏會有一個有趣的問題,就是.then中返回的究竟是一個新promise的仍是鏈式頭部的調用者?????code
從代碼上乍一看, Promise.then(...).catch(...) 像是針對最初的 Promise 對象進行了一連串的方法鏈調用。
然而實際上不論是 then 仍是 catch 方法調用,都返回了一個新的promise對象。簡單有力地證實一下對象
var beginPromise = new Promise(function (resolve) { resolve(100); }); var thenPromise = beginPromise.then(function (value) { console.log(value); }); var catchPromise = thenPromise.catch(function (error) { console.error(error); }); console.log(beginPromise !== thenPromise); // => true console.log(thenPromise !== catchPromise);// => true
顯而易見promise返回的是一個新的而非調用者
不過這樣的話難度就來了,咱們看下面代碼:
function begin() { return new Promise(resolve => { setTimeout(_ => resolve('first') , 2000) }) } begin().then(data => { console.log(data) return new Promise(resolve => { }) }).then(res => { console.log(res) });
咱們知道最後的then中函數參數永遠都不會執行,爲何說它難呢,想一下,之因此能鏈式調用是由於.then()執行以後返回了一個新的promise,必定注意,我說的新的promise是then()所返回而不是data => return new Promise....(這只是then的一個參數),這樣問題就來了,咱們從剛纔的狀況看,知道只有第一個.then中的狀態改變時第二個then中的函數參數纔會執行,放到程序上說也就是須要第一個.then中返回的promise狀態改變!即:
begin().then(data => { console.log(data) return new Promise(resolve => { setTimeout(_ => resolve('two'), 1000) }) }).then(res => { console.log(res) });
直接從代碼的角度上講,調用了第一個.then中的函數參數中的resolve以後第一個.then()返回的promise狀態也改變了,這句話有些繞,我用一張圖來說:
那麼問題就來了,咱們如何使得P2的狀態發生改變通知P1?
其實這裏用觀察者模式是能夠的,可是代價有點大,換個角度想,其實咱們直接讓P2中的resolve等於P1中的resolve不就能夠了?這樣P2中調用了resolve以後同步的P1也至關於onresolve了,上代碼:
function Promi(executor) { let _this = this; _this.$$status = 'pending'; _this.failCallBack = undefined; _this.successCallback = undefined; _this.result = undefined; _this.error = undefined; setTimeout(_ => { executor(_this.resolve.bind(_this), _this.reject.bind(_this)); }) } Promi.prototype.then = function(full, fail) { let newPromi = new Promi(_ => {}); this.successCallback = full; this.failCallBack = fail; this.successDefer = newPromi.resolve.bind(newPromi); this.failDefer = newPromi.reject.bind(newPromi); return newPromi }; Promi.prototype.resolve = function(params) { let _this = this; if (_this.$$status === 'pending') { _this.$$status = 'success'; if (!_this.successCallback) return; let result = _this.successCallback(params); if (result && result instanceof Promi) { result.then(_this.successDefer, _this.failDefer); return '' } _this.successDefer(result) } } Promi.prototype.reject = function(params) { let _this = this; if (_this.$$status === 'pending') { _this.$$status = 'fail'; if (!_this.failCallBack) return; let result = _this.failCallBack(params); if (result && result instanceof Promi) { result.then(_this.successDefer, _this.failDefer); return '' } _this.successDefer(result) } } // 測試代碼 new Promi(function(res, rej) { setTimeout(_ => res('成功'), 500) }).then(res => { console.log(res); return '第一個.then成功' }).then(res => { console.log(res); return new Promi(function(resolve) { setTimeout(_ => resolve('第二個.then成功'), 500) }) }).then(res => { console.log(res) return new Promi(function(resolve, reject) { setTimeout(_ => reject('第三個失敗'), 1000) }) }).then(res => {res console.log(res) }, rej => console.log(rej));
其實作到這裏咱們還有好多好多沒有完成,好比錯誤處理,reject處理,catch實現,.all實現,.race實現,其實原理也都差很少,(all和race以及resolve和reject其實返回的都是一個新的Promise),錯誤的傳遞?還有不少細節咱們都沒有考慮到,我這裏寫了一個還算是比較完善的:
function Promi(executor) { let _this = this; _this.$$status = 'pending'; _this.failCallBack = undefined; _this.successCallback = undefined; _this.error = undefined; setTimeout(_ => { try { executor(_this.onResolve.bind(_this), _this.onReject.bind(_this)) } catch (e) { _this.error = e; if (_this.callBackDefer && _this.callBackDefer.fail) { _this.callBackDefer.fail(e) } else if (_this._catch) { _this._catch(e) } else { throw new Error('un catch') } } }) } Promi.prototype = { constructor: Promi, onResolve: function(params) { if (this.$$status === 'pending') { this.$$status = 'success'; this.resolve(params) } }, resolve: function(params) { let _this = this; let successCallback = _this.successCallback; if (successCallback) { _this.defer(successCallback.bind(_this, params)); } }, defer: function(callBack) { let _this = this; let result; let defer = _this.callBackDefer.success; if (_this.$$status === 'fail' && !_this.catchErrorFunc) { defer = _this.callBackDefer.fail; } try { result = callBack(); } catch (e) { result = e; defer = _this.callBackDefer.fail; } if (result && result instanceof Promi) { result.then(_this.callBackDefer.success, _this.callBackDefer.fail); return ''; } defer(result) }, onReject: function(error) { if (this.$$status === 'pending') { this.$$status = 'fail'; this.reject(error) } }, reject: function(error) { let _this = this; _this.error = error; let failCallBack = _this.failCallBack; let _catch = _this._catch; if (failCallBack) { _this.defer(failCallBack.bind(_this, error)); } else if (_catch) { _catch(error) } else { setTimeout(_ => { throw new Error('un catch promise') }, 0) } }, then: function(success = () => {}, fail) { let _this = this; let resetFail = e => e; if (fail) { resetFail = fail; _this.catchErrorFunc = true; } let newPromise = new Promi(_ => {}); _this.callBackDefer = { success: newPromise.onResolve.bind(newPromise), fail: newPromise.onReject.bind(newPromise) }; _this.successCallback = success; _this.failCallBack = resetFail; return newPromise }, catch: function(catchCallBack = () => {}) { this._catch = catchCallBack } }; // 測試代碼 task() .then(res => { console.log('1:' + res) return '第一個then' }) .then(res => { return new Promi(res => { setTimeout(_ => res('第二個then'), 3000) }) }).then(res => { console.log(res) }) .then(res => { return new Promi((suc, fail) => { setTimeout(_ => { fail('then失敗') }, 400) }) }) .then(res => { console.log(iko) }) .then(_ => {}, () => { return new Promi(function(res, rej) { setTimeout(_ => rej('promise reject'), 3000) }) }) .then() .then() .then(_ => {}, rej => { console.log(rej); return rej + '處理完成' }) .then(res => { console.log(res); // 故意出錯 console.log(ppppppp) }) .then(res => {}, rej => { console.log(rej); // 再次拋錯 console.log(oooooo) }).catch(e => { console.log(e) })
還有一段代碼是我將全部的.then所有返回調用者來實現的,即全程都用一個promise來記錄狀態存儲任務隊列,這裏就不發出來了,有興趣能夠一塊兒探討下. 有時間會再完善一下all, race, resolve....不過到時候代碼結構確定會改變,實在沒啥時間,因此講究看一下吧,歡迎交流