promise 是非同步程式設計的一種解決方案,比傳統的解決方案——**函式和事件——更合理和更強大。它由社群最早提出和實現,es6 將其寫進了語言標準,統一了用法,原生提供了根據上面的訊息和定義;我們先寫個簡單的,看著有點不像的mypromise;promise
物件。所謂
promise
,簡單說就是乙個容器,裡面儲存著某個未來才會結束的事件(通常是乙個非同步操作)的結果。從語法上說,promise 是乙個物件,從它可以獲取非同步操作的訊息。promise 提供統一的 api,各種非同步操作都可以用同樣的方法進行處理。
promise
物件有以下兩個特點。(1)物件的狀態不受外界影響。
promise
物件代表乙個非同步操作,有三種狀態:pending
(進行中)、fulfilled
(已成功)和rejected
(已失敗)。只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是promise
這個名字的由來,它的英語意思就是「承諾」,表示其他手段無法改變。(2)一旦狀態改變,就不會再變,任何時候都可以得到這個結果。
promise
物件的狀態改變,只有兩種可能:從pending
變為fulfilled
和從pending
變為rejected
。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型)。如果改變已經發生了,你再對promise
物件新增**函式,也會立即得到這個結果。這與事件(event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。注意,為了行文方便,本章後面的
resolved
統一只指fulfilled
狀態,不包含rejected
狀態。有了
promise
物件,就可以將非同步操作以同步操作的流程表達出來,避免了層層巢狀的**函式。此外,promise
物件提供統一的介面,使得控制非同步操作更加容易。
promise
也有一些缺點。首先,無法取消promise
,一旦新建它就會立即執行,無法中途取消。其次,如果不設定**函式,promise
內部丟擲的錯誤,不會反應到外部。第三,當處於pending
狀態時,無法得知目前進展到哪乙個階段(剛剛開始還是即將完成)。
functionmypromise(fn)
function
reject(value)
fn(resolve, reject);
}
現在你就可以用上自定義的promise了
new mypromise((resolve, reject) =>, 2000);});//將會在2秒後輸出
解釋一下整體**:
mypromise 中的引數 fn是需要使用者傳入的自定義函式(該函式需要接收兩個引數)。
mypromise 中的內部還有兩個函式resolve和reject,其中 resolve 表示使用者的非同步任務成功時應該呼叫的函式,reject 表示使用者的非同步任務失敗時該呼叫的函式。
那使用者如何呼叫 resolve 和 reject 呢?
很簡單,把兩個函式當作 fn的引數傳遞出去即可。
所以 mypromise 內部在呼叫 fn 時會把 resolve 和 reject當作引數傳遞給 fn。
然後使用者在自定義函式內呼叫 resolve 或 reject 來通知 mypromise 非同步任務已經執行完了。
通過上面的**可以發現乙個問題,我們不知道promise的非同步任務進行到哪一步了、是成功還是失敗了。
所以增加三個狀態用來標識乙個promise的非同步任務進行到何種程度了。
pending、resolved、rejected 分別表示 執行中、已完成、已失敗。
然後通過觀察使用者呼叫的是 resolve 還是 reject 可以判斷當前promise的狀態。 那麼會有三種情況:
下面進行**的改造,定義了三個常量表示狀態以及乙個變數 state 用來儲存當前狀態。 並且當 resolve 被呼叫時將 state 修改為 resolved 。
const pedning="pending";//執行狀態
const resolved='resolved';//
以完成;
const rejected='rejected';//
以失敗function
mypromise(fn)
function
reject(err)
fn(resolve,reject);
}
ok,現在已經能知道promise當前所處的狀態了,但是任務完了得拿到結果吧,mypromise 的 resolve 被呼叫,那也只是mypromise知道任務完成了,使用者還不知道呢。 所以我們需要**函式告訴使用者,是的,其實就是**函式。 這時候就輪到 then 方法出場了,使用者通過then方法傳入**函式, mypromise 將在成功呼叫 resolve 時呼叫使用者傳入的**函式。 開始改造**,mypromise 內部需要變數儲存**函式,then 方法的作用就是將使用者傳入的**函式賦予 mypromise 內的變數。 所以 then 方法長這樣,接收兩個引數,乙個是成功時的**函式,乙個是失敗時的**函式
const pedning="pending";//執行狀態
const resolved='resolved';//
以完成;
const rejected='rejected';//
以失敗function
mypromise(fn)
function
reject(err)
fn(resolve,reject);
}mypromise.prototype.then=function
(onfulfilled,onrejected)
是的,乙個簡版promise幾乎大功告成,讓我們再試試在瀏覽器執行如下**(注意我刪除了 resolve 和 reject 裡的console語句);咱們來使用一下:
(function()
function
reject(err)
fn(resolve,reject);
}mypromise.prototype.then=function
(onfulfilled,onrejected)
new mypromise((resolve,reject)=>,4000);
}).then((value)=>)
})()
通過匿名函式和函式自執行,形成區域性作用域,保護裡面的變數;
上面的**,用法上已經和promise長得差不多了,但是如果我們多次呼叫 then 方法呢? 是的,只有最後乙個 then 方法裡的**函式能執行,這當然沒法滿足我們的需要。 於是,將兩個**函式改成函式陣列(請回想一下前置知識),並在狀態更改時遍歷呼叫**函式。 改造後的**如下:
(function()
function
reject(err)
fn(resolve,reject);
}mypromise.prototype.then=function
(onfulfilled,onrejected)
new mypromise((resolve,reject)=>,5000)
}).then((val)=>).then((val)=>)
})()
上面已經是簡版promise的實現了。 但是我們還可以更完善一點,增強 mypromise 的健壯性。 例如,若使用者自定義函式在執行過程中發生了錯誤,會中斷程式的執行,於是我們增加try...catch...語句,並在發生錯誤時主動執行reject函式告知使用者。
trycatch
(e)
又或者,對引數進行校驗,狀態進行判斷等,以 then為例,若使用者傳入的引數不是函式呢? 或者promise的狀態已經時rejected或resolved,此時呼叫then呢?
改造 then 後**如下:
mypromise.prototype.then = function(onfulfilled, onrejected)
if(typeof onfulfilled !== 'function') ;
}const that = this
;
if (that.state ===pending)
if (that.state ===resolved)
if (that.state ===rejected)
}
手寫乙個Promise
js物件導向 在js中一切皆物件,但js並不是一種真正的物件導向 oop 的語言,因為它缺少類 class 的概念。雖然es6引入了class和extends,使我們能夠輕易地實現類和繼承。但js並不存在真實的類,js的類是通過函式以及原型鏈機制模擬的,本小節的就來 如何在es5環境下利用函式和原型...
手寫乙個promise
promise a 規範 注 以下 沒有通過promises aplus tests的全部測試,但基本功能還是全的 測試結果 864 passing,8 failing 另外可以參考這個指南中的 promise實現 promise resolve 100 規範 class mypromise con...
手寫系列 帶你實現乙個簡單的Promise
學習之前 需要先對promise有個基本了解哦,這裡都預設大家都是比較熟悉promise的 本次將帶小夥伴們實現promise的基本功能 promise的基本骨架 promise的then promise.then的多次呼叫 then鏈式呼叫 catch的實現 finally的實現 const pr...