從去年ES2015發佈至今,已通過去了一年多,ES2015發佈的新的語言特性中最爲流行的也就莫過於Promise了,Promise使得現在JavaScript異步編程如此輕鬆愜意,甚至慢慢遺忘了曾經那不堪回首的痛楚。其實從JavaScript誕生,JavaScript中的異步編程就已經出現,例如點擊鼠標、敲擊鍵盤這些事件的處理函數都是異步的,時間到了2009年,Node.js橫空出世,在整個Node.js的實現中,將回調模式的異步編程機制發揮的淋漓盡致,Node的流行也是的愈來愈多的JavaScripter開始了異步編程,可是回調模式的反作用也慢慢展示在人們眼前,錯誤處理不夠優雅以及嵌套回調帶來的「回調地獄」。這些反作用使得人們從回調模式的溫柔鄉中慢慢清醒過來,開始尋找更爲優雅的異步編程模式,路漫漫其修遠兮、吾將上下而求索。時間到了2015年,Promise拯救那些苦苦探索的先驅。行使它歷史使命的時代彷佛已經到來。javascript
每一個事物的誕生有他的歷史使命,更有其歷史成因,促進其被那些探索的先驅們所發現。瞭解nodejs或者熟悉瀏覽器的人都知道,JavaScript引擎是基於事件循環或單線程這兩個特性的。更爲甚者在瀏覽器中,更新UI(也就是瀏覽器重繪、重拍頁面佈局)和執行JavaScript代碼也在一個單線程中,可想而知,一個線程就至關於只有一條馬路,若是一輛馬車拋錨在路上了阻塞了馬路,那麼別的馬車也就擁堵在了那兒,這個單線程容易被阻塞是一個道理,單線程也只能容許某一時間點只可以執行一段代碼。同時,JavaScript沒有想它的哥哥姐姐們那麼財大氣粗,像Java或者C++,一個線程不夠,那麼再加一個線程,這樣就可以同時執行多段代碼了,可是這樣就會帶來的隱患就是狀態不容易維護,JavaScript選擇了單線程非阻塞式的方式,也就是異步編程的方式,就像上面的馬車拋錨在了路上,那麼把馬車推到路邊的維修站,讓其餘馬車先過去,等馬車修好了再回到馬路上繼續行駛,這就是單線程非阻塞方式。正如Promise的工做方式同樣,經過Promise去向服務器發起一個請求,畢竟請求有網絡開銷,不可能立刻就返回請求結果的,這個時候Promise就處於pending狀態,可是其並不會阻塞其餘代碼的執行,當請求返回時,修改Promise狀態爲fulfilled或者rejected(失敗請求)。同時執行綁定到這兩個狀態上面的「處理函數」。這就是異步編程的模式,也就是Promise兢兢業業的工做方式,在下面一個部分將詳細討論Promise。java
怎麼一句話解釋Promise呢?Promise能夠代指那些還沒有完成的一些操做,可是其在將來的某個時間會返回某一特定的結果。 node
當建立一個Promise實例後,其表明一個未知的值,在未來的某個時間會返回一個成功的返回值,或者失敗的返回值,咱們能夠爲這些返回值添加處理函數,當值返回時,處理函數被調用。Promise老是處於下面三種狀態之一:git
pending: Promise的初始狀態,也就是未被fulfilled或者rejected的狀態。es6
fulfilled: 意味着promise代指的操做已經成功完成。github
rejected:意味着promise代指的操做因爲某些緣由失敗。編程
一個處於pending狀態的promise可能因爲某個成功返回值而發展爲fulfilled狀態,也有可能由於某些錯誤而進入rejected狀態,不管是進入fulfilled狀態或者rejected狀態,綁定到這兩種狀態上面的處理函數就會被執行。而且進入fulfilled或者rejected狀態也就不能再返回pending狀態了。promise
上面說了那麼多,其實都是鋪墊。接下來咱們就開始實現本身的Promise對象。go go go!!!瀏覽器
Promise有三種狀態,pending、fulfilled、rejected。緩存
const PENDING = 'PENDING' // Promise 的 初始狀態 const FULFILLED = 'FULFILLED' // Promise 成功返回後的狀態 const REJECTED = 'REJECTED' // Promise 失敗後的狀態
有了三種狀態後,那麼咱們怎麼建立一個Promise實例呢?
const promise = new Promise(executor) // 建立Promise的語法
經過上面生成promise語法咱們知道,Promise實例是調用Promise構造函數經過new操做符生成的。這個構造函數咱們能夠先這樣寫:
class Promise { constructor(executor) { this.status = PENDING // 建立一個promise時,首先進行狀態初始化。pending this.result = undefined // result屬性用來緩存promise的返回結果,能夠是成功的返回結果,或失敗的返回結果 } }
咱們能夠看到上面構造函數接受的參數executor。它是一個函數,而且接受其餘兩個函數(resolve和reject)做爲參數,當resolve函數調用後,promise的狀態轉化爲fulfilled,而且執行成功返回的處理函數(不用着急後面會說到怎麼添加處理函數)。當reject函數調用後,promise狀態轉化爲rejected,而且執行失敗返回的處理函數。
如今咱們的代碼大概是這樣的:
class Promise { constructor(executor) { this.status = PENDING this.result = undefined executor(data => resolveProvider(this, data), err => rejectProvider(this, err)) } } function resolveProvider(promise, data) { if (promise.status !== PENDING) return false promise.status = FULFILLED } function rejectProvider(promise, data) { if (promise.status !== PENDING) return false promise.status = FULFILLED }
Dont Repeat Yourselt!!!咱們能夠看到上面代碼後面兩個函數基本相同,其實咱們能夠把它整合成一個函數,在結合高階函數的使用。
const statusProvider = (promise, status) => data => { if (promise.status !== PENDING) return false promise.status = status promise.result = data } class Promise { constructor(executor) { this.status = PENDING this.result = undefined executor(statusProvider(this, FULFILLED), statusProvider(this, REJECTED)) } }
如今咱們的代碼就看上去簡潔多了。
其實經過 new Promise(executor)
已經能夠生成一個Promise實例了,甚至咱們能夠經過傳遞到executor中的resolve和reject方法來改變promise狀態,可是!如今的promise依然沒啥卵用!!!由於咱們並無給它添加成功和失敗返回的處理函數。
首先咱們須要給咱們的promise增長兩個屬性,successListener和failureListener用來分別緩存成功處理函數和失敗處理函數。
class Promise { constructor(executor) { this.status = PENDING this.successListener = [] this.failureListener = [] this.result = undefined executor(statusProvider(this, FULFILLED), statusProvider(this, REJECTED)) } }
怎麼添加處理函數呢?ECMASCRIPT標準中說到,咱們能夠經過promise原型上面的then方法爲promise添加成功處理函數和失敗處理函數,能夠經過catch方法爲promise添加失敗處理函數。
const statusProvider = (promise, status) => data => { if (promise.status !== PENDING) return false promise.status = status promise.result = data switch(status) { case FULFILLED: return promise.successListener.forEach(fn => fn(data)) case REJECTED: return promise.failurelistener.forEach(fn => fn(data)) } } class Promise { constructor(executor) { this.status = PENDING this.successListener = [] this.failurelistener = [] this.result = undefined executor(statusProvider(this, FULFILLED), statusProvider(this, REJECTED)) } /** * Promise原型上面的方法 */ then(...args) { switch (this.status) { case PENDING: { this.successListener.push(args[0]) this.failurelistener.push(args[1]) break } case FULFILLED: { args[0](this.result) break } case REJECTED: { args[1](this.result) } } } catch(arg) { return this.then(undefined, arg) } }
咱們如今的Promise基本初具雛形了。甚至能夠運用到一些簡單的場景中了。舉個例子。
/*建立一個延時resolve的pormise*/ new Promise((resolve, reject) => {setTimeout(() => resolve(5), 2000)}).then(data => console.log(data)) // 5 /*建立一個及時resolve的promise*/ new Promise((resolve, reject) => resolve(5)).then(data => console.log(data)) // 5 /*鏈式調用then方法還不可以使用!*/ new Promise(resolve=> resolve(5)).then(data => data).then(data => console.log(data)) // Uncaught TypeError: Cannot read property 'then' of undefined
Promise須要實現鏈式調用,咱們須要再次回顧下then方法的定義:
then方法爲pormise添加成功和失敗的處理函數,同時then方法返回一個新的promise對象,這個新的promise對象resolve處理函數的返回值,或者當沒有提供處理函數時直接resolve原始的值。
能夠看出,promise可以鏈式調用歸功於then方法返回一個全新的promise,而且resolve處理函數的返回值,固然,若是then方法的處理函數自己就返回一個promise,那麼久不用咱們本身手動生成一個promise了。瞭解了這些,就開始動手寫代碼了。
const isPromise = object => object && object.then && typeof object.then === 'function' const noop = () => {} const statusProvider = (promise, status) => data => { // 同上面代碼 } class Promise { constructor(executor) { // 同上面代碼 } then(...args) { const child = new this.constructor(noop) const handler = fn => data => { if (typeof fn === 'function') { const result = fn(data) if (isPromise(result)) { Object.assign(child, result) } else { statusProvider(child, FULFILLED)(result) } } else if(!fn) { statusProvider(child, this.status)(data) } } switch (this.status) { case PENDING: { this.successListener.push(handler(args[0])) this.failurelistener.push(handler(args[1])) break } case FULFILLED: { handler(args[0])(this.result) break } case REJECTED: { handler(args[1])(this.result) break } } return child } catch(arg) { return this.then(undefined, arg) } }
首先咱們寫了一個isPromise方法,用於判斷一個對象是不是promise。就是判斷對象是否有一個then
方法,免責聲明爲了實現上的簡單,咱們不區分thenable和promise的區別,可是咱們應該是知道。全部的promise都是thenable的,而並非全部的thenable對象都是promise。(thenable對象是指帶有一個then方法的對象,該then方法其實就是一個executor。)isPromise的做用就是用於判斷then方法返回值是不是一個promise,若是是promise,就直接返回該promise,若是不是,就新生成一個promise並返回該promise。
因爲須要鏈式調用,咱們對successListener和failureListener中處理函數進行了重寫,並非直接push進去then方法接受的參數函數了,由於then方法須要返回一個promise,因此當then方法裏面的處理函數被執行的同時,咱們也須要對then方法返回的這個promise進行處理,要麼resolve,要麼reject掉。固然,大部分狀況都是須要resolve掉的,只有當then方法沒有添加第二個參數函數,同時調用then方法的promise就是rejected的時候,才須要把then方法返回的pormise進行reject處理,也就是調用statusProvider(child, REJECTED)(data)
.
toy Promise實現的完整代碼:
const PENDING = 'PENDING' // Promise 的 初始狀態 const FULFILLED = 'FULFILLED' // Promise 成功返回後的狀態 const REJECTED = 'REJECTED' // Promise 失敗後的狀態 const isPromise = object => object && object.then && typeof object.then === 'function' const noop = () => {} const statusProvider = (promise, status) => data => { if (promise.status !== PENDING) return false promise.status = status promise.result = data switch(status) { case FULFILLED: return promise.successListener.forEach(fn => fn(data)) case REJECTED: return promise.failurelistener.forEach(fn => fn(data)) } } class Promise { constructor(executor) { this.status = PENDING this.successListener = [] this.failurelistener = [] this.result = undefined executor(statusProvider(this, FULFILLED), statusProvider(this, REJECTED)) } /** * Promise原型上面的方法 */ then(...args) { const child = new this.constructor(noop) const handler = fn => data => { if (typeof fn === 'function') { const result = fn(data) if (isPromise(result)) { Object.assign(child, result) } else { statusProvider(child, FULFILLED)(result) } } else if(!fn) { statusProvider(child, this.status)(data) } } switch (this.status) { case PENDING: { this.successListener.push(handler(args[0])) this.failurelistener.push(handler(args[1])) break } case FULFILLED: { handler(args[0])(this.result) break } case REJECTED: { handler(args[1])(this.result) break } } return child } catch(arg) { return this.then(undefined, arg) } }
在ECMAScript標準中,Promise構造函數上面還提供了一些靜態方法,好比Promise.resolve
、Promise.reject
、Promsie.all
、Promise.race
。當咱們有了上面的基礎實現後,爲咱們的toy Promise添加上面這些新的功能必定能讓其更加實用。
在咱們的基本實現中,咱們並無區分thenable對象,其實Promise.resolve
和then
方法均可以接受一個thenable對象,並把該thenable對象轉化爲一個promise對象,若是想讓咱們的toy Promise用於生產的話,這也是要考慮的。
爲了讓咱們的toy Promise變得更強壯,咱們須要擁有強健的錯誤處理機制,好比驗證executor必須是一個函數、then方法的參數只能是函數或者undefined或null,又好比executor和then方法中拋出的錯誤並不可以被window.onerror監測到,而只可以經過錯誤處理函數來處理,這也是須要考慮的因素。
若是咱們的Promise polyfill是考慮支持多平臺,那麼首要考慮的就是瀏覽器環境或Node.js環境,其實在這兩個平臺,原生Promise都是支持兩個事件的。就拿瀏覽器端舉例:
unhandledrejection
: 在一個事件循環中,若是咱們沒有對promise返回的錯誤進行處理,那麼就會在window對象上面觸發該事件。
rejectionhandled
:若是在一個事件循環後,咱們纔去對promise返回的錯誤進行處理,那麼就會在window對象上面監聽到此事件。
關於這兩個事件以及node.js平臺上面相似的事件請參考Nicholas C. Zakas新書<understanding es6>
Promise可以很棒的處理異步編程,要想學好它我認爲最好的方法就是親自動手去實現一個本身的Promise,下面的項目Jocs/promise是個人實現,歡迎你們pr和star。