參考文章:深刻理解 Promise、【翻譯】Promises/A+規範html
從入門Promise的正確姿式中咱們已經瞭解到Promise的基本用法。那麼如今給你一個需求:根據Promise的用法和Promise/A+規範,實現一個本身的Promise函數。編程
根據Promise的用法咱們知道:在new一個Promise函數的時候,Promise函數必須接受一個函數做爲參數。咱們暫且把這個參數函數稱爲執行器。數組
執行器提供兩個參數(resolve,reject),且內部有狀態機制(pending,resolved,rejected)。Promise構造函數的原型上有then方法。promise
咱們再來看一下PromiseA+標準中是怎樣規定的:緩存
Pending
,在被 resolve
或 reject
時,狀態變爲 Fulfilled
或 Rejected
then
方法,且只接受兩個函數參數 onFulfilled
、onRejected
由以上標準就容易就能實現這個類的大體結構。app
開始擼:異步
var PENDING = 'pending', RESOLVED = 'resolved', REJECTED = 'rejected', function Promise(executor) { //養成良好的編程習慣,先對參數作一個容錯處理 if (executor && typeof executor != 'function') { throw new Error('Promise is not a function'); } let _this = this;//緩存this _this.status = PENDING;//當前Promise的狀態 _this.value = undefined;//Promise成功執行時要傳遞給回調的數據,默認爲undefined _this.reason = undefined;//Promise失敗執行時傳遞給毀掉的緣由,默認爲undefined function resolve(value) { //內置一個resolve方法 if (_this.status == PENDING) { _this.status = RESOLVED;//當調用resolve時,將Promise的狀態改成resoled _this.value = value;//保存成功調用時傳遞進來的數據 } } function reject(reason) { if (_this.status == PENDING) { _this.status = REJECTED;//當調用reject時,將Promise的狀態改成rejected _this.reason = reason;//保存失敗調用時傳遞進來的緣由 } } executor(resolve,reject); } //then方法能夠接受兩個函數參數,分別表示當前Promise執行成功時的調用onFulfilled和執行失敗時調用onRejected Promise.prototype.then = function(onFulfilled,onRejected) { let _this = this,//緩存this,保不齊後面會用到,固然若是你不想緩存this,也能夠在後面使用箭頭函數 if (_this.status == RESOLVED) { onFulfilled(_this.value); } if (_this.status == REJECTED) { onFulfilled(_this.reason); } }
看起來不錯,但回調函數是當即執行的,也就是說上面實現的Promise只支持同步代碼,而沒法進行異步操做,好比這樣是不行的函數
let p = new Promise(function(resolve, reject){ setTimeout(function(){ resolve('成功執行了!'); }, 1000) }) p.then(function(data){ console.log('成功', data) },function(err){ console.log('失敗', err) }) // 不會輸出任何代碼
緣由是:咱們在then函數中只對成功態和失敗態進行了判斷,而實例被new時,執行器中的代碼會當即執行,但setTimeout中的代碼將稍後執行,也就是說,then方法執行時,Promise的狀態沒有被改變依然是pending態。post
因此咱們要對pending態也作判斷,而因爲代碼多是異步的,因此回調函數就不應被當即執行,因此咱們就要想辦法把回調函數進行緩存,當狀態改變後(由pending態變成resolved或rejected態),再執行相應的函數。this
那麼狀態在何時改變呢:很顯然,在執行resolve或rejected函數的時候,狀態會發生改變。
而且,then方法是能夠屢次使用的,因此要能存多個回調,那麼這裏咱們用一個數組來存儲多個回調函數。
在實例上掛載兩個參數
_this.onResolvedCallbacks = []; // 存放then成功的回調 _this.onRejectedCallbacks = []; // 存放then失敗的回調
咱們再給then方法加一個pending時的判斷
if(_this.status === 'pending'){ // 每一次then時,若是是等待態,就把回調函數push進數組中,何時改變狀態何時再執行 _this.onResolvedCallbacks.push(function(){ // 這裏用一個函數包起來,是爲了後面加入新的邏輯進去 onFulfilled(_this.value) }) _this.onRejectedCallbacks.push(function(){ // 同理 onRjected(_this.reason) }) }
下一步要分別在resolve和reject方法里加入執行數組中存放的函數的方法,修改一下上面的resolve和reject方法
function resolve(value) { if (_this.status === 'pending') { _this.status = 'resolved' _this.value = value _this.onResolvedCallbacks.forEach(function(fn){ // 當成功的函數被調用時,以前緩存的回調函數會被一一調用 fn() }) } } function reject(reason) { if (_this.status === 'pending') { _this.status = 'rejected' _this.reason = reason _this.onRejectedCallbacks.forEach(function(fn){// 當失敗的函數被調用時,以前緩存的回調函數會被一一調用 fn() }) } }
如今能夠執行異步任務了,也能夠給一個Promise函數屢次then了。
上面的代碼看似很完美,前提是整個Promise函數可以正確執行,那樣就沒reject函數什麼事了。但咱們必需要考慮的一種狀況是:當Promise函數執行出錯時怎麼辦,因此一個健壯的Promise函數是應該有錯誤處理機制的。因此咱們應該在Promise的代碼體中加入try catch,若是出現異常,則捕捉錯誤交給reject。
咱們實現一下,思路很簡單,在執行器執行時進行try catch
try{ executor(resolve, reject) }catch(e){ // 若是捕獲發生異常,直接調失敗,並把參數穿進去 reject(e) }
上面說過了,then能夠鏈式調用,也是這一點讓Promise十分好用,固然這部分源碼也比較複雜
咱們知道jQuery實現鏈式調用是return了一個this,但Promise不行,爲何不行?
由於then函數內返回的是一個新的Promise對象。咱們看一下標準是如何定義的:
標準中規定:
then
方法必須返回一個新的 Promise實例
(ES6中的標準,Promise/A+中沒有明確說明)
then
中回調的執行順序,onFulfilled
或 onRejected
必須異步調用因此咱們要作的就是:在then方法中先定義一個新的Promise,取名爲promise2,而後在三種狀態下分別用promise2包裝一下,在調用onFulfilled時用一個變量x接收返回值,try catch一下代碼,沒錯就調resolve傳入x,有錯就調reject傳入錯誤,最後再把promise2給return出去,就能夠進行鏈式調用了
//修改then Promise.prototype.then = function(onFulfilled,onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function(value) {return vaule}; onRejected = typeof onRejected === 'function' ? onFulfilled : function(err) {throw err}; let _this = this;//緩存this,保不齊後面會用到,固然若是你不想緩存this,也能夠在後面使用箭頭函數 let promise2; if (_this.status === RESOLVED) { promise2 = new Promise(function(resolve,reject) { try { //onFulfilled函數的執行狀況要考慮多種狀況,後面會細說 let x = onFulfilled(_this.value);//將執行onFulfilled的返回值傳給x,這裏須要注意的是執行過程當中有可能會出錯 resolve(x); } catch(e) { reject(e); } }) } if (_this.status === REJECTED) { promise2 = new Promise(function(resolve,reject) { try { //onFulfilled函數的執行狀況要考慮多種狀況,後面會細說 let x = onRejected(_this.reason);//將執行onFulfilled的返回值傳給x(即此次then函數執行的返回值),這裏須要注意的是執行過程當中有可能會出錯 resolve(x);//這個x會被下一次的then函數接收到 } catch(e) { reject(e); } }) } if(_this.status === PENDING){ promise2 = new Promise(function(resolve,reject) { // 每一次then時,若是是等待態,就把回調函數push進數組中,何時改變狀態何時再執行 _this.onResolvedCallbacks.push(function(){ // 這裏用一個函數包起來,是爲了後面加入新的邏輯進去 try { let x = onFulfilled(_this.value); resolve(x); } catch(e) { reject(e); } }) _this.onRejectedCallbacks.push(function(){ // 同理 try { let x = onRejected(_this.reason); resolve(x); } catch(e) { reject(e); } }) }) } }
上面的實現雖然能用,可是很粗糙。
在接下的分析以前,我但願你們可以清晰的明白x的值表明什麼:x表示的是上一次的then函數或promise函數執行結果的返回值,這個x的值會被resolve(x),做爲下一次then函數調用時的參數。
明確了x的值之後,接下來對onFulfilled和onRejected函數可能出現的狀況作一個列舉,這也是爲了咱們的Promise函數能作到最大的容錯率:
var p1 = p.then(function(){// 這裏得用var,let因爲做用域的緣由會報錯undefined return p1 })
5.前一次then返回的是一個別人本身隨便寫的Promise,這個Promise多是個有then的普通對象,好比{then:'哈哈哈'},也有可能在then裏故意拋錯(這種蛋疼的 操做咱們也要考慮進去)。好比他這樣寫
let promise = {} Object.defineProperty(promise,'then',{ value: function(){ throw new Error('報錯氣死你') } }) // 若是返回這東西,咱們再去調then方法就確定會報錯了
6.調resolve的時候再傳一個Promise下去,咱們還得處理這個Promise。
p.then(function(data) { return new Promise(function(resolve, reject) { resolve(new Promise(function(resolve,reject){ resolve(1111) })) }) })
7.可能既調resolve又調reject,得忽略後一個。
8.光then,裏面啥也不寫。
問題8最好解決,咱們只要手動添加一個onFulfilled和onRejected進去就行了。
對於問題1-7,咱們能夠採起統一的以爲方案,定義一個函數來判斷和處理這一系列的狀況,官方給出了一個叫作resolvePromise的函數。
因此,咱們進一步來完善then方法。
Promise.prototype.then = function(onFulfilled,onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function(value) {return value}; onRejected = typeof onRejected === 'function' ? onRejected : function(err) {throw err};
//這裏正好解釋了在正式的Promise函數中,咱們爲何能夠不寫onRejected函數,
//由於then方法內部會幫咱們封裝好一個onRejected函數,用來拋出上一次then或Promise執行出錯的信息,這也是爲何能夠在最後執行catch方法的緣由 let _this = this;//緩存this,保不齊後面會用到,固然若是你不想緩存this,也能夠在後面使用箭頭函數 let promise2; if (_this.status === RESOLVED) { promise2 = new Promise(function(resolve,reject) { try { //onFulfilled函數的執行狀況要考慮多種狀況,後面會細說 let x = onFulfilled(_this.value);//將執行onFulfilled的返回值傳給x,這裏須要注意的是執行過程當中有可能會出錯 resolvePromise(promise2,x,resolve,reject); } catch(e) { reject(e); } }) } if (_this.status === REJECTED) { promise2 = new Promise(function(resolve,reject) { try { //onFulfilled函數的執行狀況要考慮多種狀況,後面會細說 let x = onRejected(_this.reason);//將執行onFulfilled的返回值傳給x(即此次then函數執行的返回值),這裏須要注意的是執行過程當中有可能會出錯 resolvePromise(promise2,x,resolve,reject);//這個x會被下一次的then函數接收到 } catch(e) { reject(e); } }) } if(_this.status === PENDING){ promise2 = new Promise(function(resolve,reject) { // 每一次then時,若是是等待態,就把回調函數push進數組中,何時改變狀態何時再執行 _this.onResolvedCallbacks.push(function(){ // 這裏用一個函數包起來,是爲了後面加入新的邏輯進去 try { let x = onFulfilled(_this.value); resolvePromise(promise2,x,resolve,reject); } catch(e) { reject(e); } }) _this.onRejectedCallbacks.push(function(){ // 同理 try { let x = onRejected(_this.reason); resolvePromise(promise2,x,resolve,reject); } catch(e) { reject(e); } }) }) } return promise2; }
定義resolvePromise方法:
function resolvePromise(promise2, x, resolve, reject) { // 接受四個參數: 新的Promise、返回值,成功和失敗的回調 // 有可能這裏返回的x是別人的promise // 儘量容許其餘亂寫 if (promise2 === x) { //這裏應該報一個類型錯誤,來解決問題4 return reject(new TypeError('循環引用了')) } // 看x是否是一個promise,promise應該是一個對象 let called = false; // 表示是否調用過成功或者失敗,用來解決問題7 //下面判斷上一次then返回的是普通值仍是函數,來解決問題一、2 if (x !== null && (typeof x === 'object' || typeof x === 'function')) { // 多是promise {},看這個對象中是否有then方法,若是有then我就認爲他是promise了 try { let then = x.then;// 保存一下x的then方法 if (typeof then === 'function') { // 成功 //用call方法修改指針爲x,不然this指向window then.call(x, function (y) {//若是x是一個Promise對象,y參數表示x執行後的resolve值 //console.log(y); if (called) return //若是調用過就return掉 called = true // y可能仍是一個promise,在去解析直到返回的是一個普通值 resolvePromise(promise2, y, resolve, reject)//遞歸調用,解決了問題6 }, function (err) { //失敗時執行的函數 if (called) return called = true console.log('r'); reject(err); }) } else {//若是x不是一個Promise對象,則直接resolve(x) resolve(x) } } catch (e) { if (called) return called = true; reject(e); } } else { // 說明是一個普通值 resolve(x); // 表示成功了 } }
因爲catch方法是then(null, onRejected)的語法糖,因此這裏也很好實現。
Promise.prototype.catch = function(onRejected) { return this.then(null,onRejected); }
至此,一個簡易粗糙的Promise函數已經實現了。同窗們能夠本身用本身寫的這個Promise函數驗證一下是不是否可行。