使用Promise是極好的,它是如此有用以致於我以爲應該好好研究一下Promise,甚至是實現一個簡易的版本。實現以前,咱們先來看看Promise的用途:html
Promise的第一個用途是可以很好地解決回調黑洞的問題,假設要實現一個用戶展現的任務,這個任務分爲三步:node
獲取用戶信息git
獲取用戶圖像github
彈窗提示編程
不使用Promise,咱們的實現多是這樣子:數組
getUserInfo(id, function (info) { getUserImage(info.img, function () { showTip(); }) })
這裏只是三步,若是有更長串的任務時,咱們就會陷入到回調黑洞之中,爲了解決這個問題,咱們就可使用Promise來處理這一長串任務,使用Promise的版本是這樣子的:promise
// getUserInfo返回promise getUserInfo(id) .then(getUserImage) .then(showTip) .catch(function (e) { console.log(e); });
原來向右發展的代碼,開始向下發展,這樣也更適合編程習慣,若是要讓咱們的代碼更加健壯,咱們就須要在每一步來處理錯誤信息,使用promise這後,咱們只須要在最後的catch中作善後處理。瀏覽器
假如咱們要顯示某一個頁的10條記錄,可是咱們只有一個經過id獲取記錄的接口,這樣咱們就須要發送10個請求,而且全部請求都完成以後再將記錄所有添加到頁面之中,Promise在這個場景下使用是特別合適的。併發
代碼多是這樣子:異步
// ids要獲取信息的全部記錄id // getRecordById獲取記錄的接口,返回promise Promise.all(ids.map(getRecordById)) .then(showRecords) .catch(function (e) { console.log(e); });
這就是Promise的一些簡單的用途,固然使人興奮的是Promise已是ES6的標準,並且目前不少瀏覽器已經原生支持Promise了。對於那些沒法使用Promise的瀏覽器,咱們就只能本身去實現了,下面就來看看Promise的簡單實現吧。
先來盜用一張MDN的圖,先來熱熱身,看看Promise的狀態遷移:
Promise有三種狀態:
pending:初始狀態, 非 fulfilled 或 rejected
fulfilled: 成功的操做
rejected: 失敗的操做
咱們能夠看出新建的Promise是pending狀態,fulfill以後就會執行調用then的回調函數了,假若reject了就會調用catch來進行異常處理了,而且不管是調用then仍是catch都會返回新的promise,這就是爲何promise能夠鏈式調用了。
接着,咱們來研究一下規範是怎麼描述
promise的。這裏只抽取核心部分,邊界問題不考慮。
檢查參數:例如executor是否是函數啊
初始化:[[State]]=pending
,[[FulfillReactions]]=[]
,[[RejectReactions]]=[]
建立resolve對象:{[[Resolve]]: resolve, [[Reject]]: reject}
執行executor:executor(resolve, reject)
所以構造函數裏面傳入的excuter是當即被執行的。FulfillReactions存儲着promise執行成功時要作的操做,RejectReactions存儲着promise是要執行的操做。
function Promise(resolver) { this._id = counter++; this._state = PENDING; this._result = undefined; this._subscribers = []; var promise = this; if (noop !== resolver) { try { resolver(function (value) { resolve(promise, value); }, function (reason) { reject(promise, reason); }); } catch (e) { reject(promise, e); } } }
檢查[[state]],必須爲pending(不是pending的表示已經解析,不能重複解析)
賦值:[[Result]]=value
,[[state]]=fulfilled
觸發[[FulfillReactions]]的操做
和FulfillPromise聯繫最緊密的就是ResolvePromise了,這裏咱們給出的是ResolvePromise的實現,區別只是多了直接解析Promise。
function resolve(promise, value) { // 要resolve的爲promise(then的callback返回的是promise) if (typeof value === 'object' && promise.constructor === value.constructor) { handleOwnThenable(promise, value); } // 要resolve的是值 else { if (promise._state !== PENDING) { return; } promise._result = value; promise._state = FULFILLED; asap(publish, promise); } } function handleOwnThenable(promise, thenable) { // 若是返回的promise已經完成 // 直接用該promise的值resolve父promise if (thenable._state === FULFILLED) { resolve(promise, thenable._result); } else if (thenable._state === REJECTED) { reject(promise, thenable._result); } // 若是返回的promise未完成 // 要等該promise完成再resolve父promise else { subscribe(thenable, undefined, function(value) { resolve(promise, value); }, function(reason) { reject(promise, reason); }); } }
檢查[[state]],必須爲pending(不是pending的表示已經解析,不能重複解析)
賦值:[[Result]]=reason
,[[state]]=rejected
觸發[[RejectReactions]]的操做
觸發[[FulfillReactions]]和觸發[[RejectReactions]]實際就是遍歷數組,執行全部的回調函數。
function reject(promise, reason) { if (promise._state !== PENDING) { return; } promise._state = REJECTED; promise._result = reason; asap(publish, promise); }
promise=this
新建resultCapability三元組,{[[Promise]], [[Resolve]], [[Reject]]}
([[Promise]]新建的)
fulfillReaction={[[Capabilities]]: resultCapability, [[Handler]]: onFulfilled}
rejectReaction={[[Capabilities]]: resultCapability, [[Handler]]: onRejected}
若是[[state]]是pending:fulfillReaction加入[[FulfillReactions]],rejectReaction加入[[RejectReactions]]
若是[[state]]是fulfilled:fulfillReaction加入執行隊列
若是[[state]]是rejected:rejectReaction加入執行隊列
返回resultCapability.[[Promise]]
這裏能夠看出構造函數和then的關係是很緊密的,新建的promise若是是異步操做,那麼狀態就是pending,調用then時會新建子promise,而且將回調操做加入父promise的[[FulfillReactions]]或[[RejectReactions]]的數組裏,這實際就是發佈訂閱模式。
他們是這樣的關係:
不管是new promise仍是調用then或catch,都會獲得一個新的promise,這些promise都會訂閱父級promise的完成事件,父級promise完成以後就會執行一系列的回調操做,也就是發佈。
then的語法糖:then(null, onRejected)
下面就是Promise原型:
Promise.prototype = { constructor: Promise, then: function (onFulfillment, onRejection) { var parent = this; var state = parent._state; if (state === FULFILLED && !onFulfillment || state === REJECTED && !onRejection) { return this; } var child = new Promise(noop); var result = parent._result; if (state) { var callback = arguments[state - 1]; asap(function () { invokeCallback(state, child, callback, result); }); } else { subscribe(parent, child, onFulfillment, onRejection); } return child; }, 'catch': function (onRejection) { return this.then(null, onRejection); } };
新建promise
調用ResolvePromise(promise, value)
(未列出,會判斷一些狀況而後調用FulfillPromise)
返回promise
Promise.resolve = function (arg) { var child = new Promise(noop); resolve(child, arg); return child; };
新建promise
調用RejectPromise(promise, value)
返回promise
Promise.reject = function (reason) { var child = new Promise(noop); reject(child, reason); return child; };
到這裏咱們已經可以實現基本的promise了,Promise.all
和Promise.race
就不繼續描述了,有興趣的能夠繼續去讀規範,這裏上圖來講明我對這兩個函數的理解:
調用promise.all會新建一個對象來存儲全部promise的處理狀態,保存執行的結果,當remain爲0時,就能夠resolve 新建的promise,這樣就能夠繼續日後執行了。
Promise.all = function (promises) { var child = new Promise(noop); var record = { remain: promises.length, values: [] }; promises.forEach(function (promise, i) { if (promise._state === PENDING) { subscribe(promise, undefined, onFulfilled(i), onRejected); } else if (promise._state === REJECTED) { reject(child, promise._result); return false; } else { --record.remain; record.values[i] = promise._result; if (record.remain == 0) { resolve(child, values); } } }); return child; function onFulfilled(i) { return function (val) { --record.remain; record.values[i] = val; if (record.remian === 0) { resolve(child, record.values); } } } function onRejected(reason) { reject(child, reason); } };
promise.race與promise.all相似,不過只要有一個promise完成了,咱們就能夠resolve新建的promise了。
Promise.race = function (promises) { var child = new Promise(noop); promises.forEach(function (promise, i) { if (promise._state === PENDING) { subscribe(promise, undefined, onFulfilled, onRejected); } else if (promise._state === REJECTED) { reject(child, promise._result); return false; } else { resolve(child, promise._result); return false; } }); return child; function onFulfilled(val) { resolve(child, val); } function onRejected(reason) { reject(child, reason); } };
這就是promise的基本內容了,完整代碼請戳這裏。
若是傳入then裏面的參數不是函數,就會被忽略,這就是promise穿透的緣由,因此永遠往then裏面傳遞函數。答案能夠從then方法裏面調用的一個關鍵函數invokeCallback中找到答案:
function invokeCallback(settled, promise, callback, detail) { var hasCallback = (typeof callback === 'function'), value, error, succeeded, failed; if (hasCallback) { try { value = callback(detail); } catch (e) { value = { error: e }; } if (value && !!value.error) { failed = true; error = value.error; value = null; } else { succeeded = true; } } // then的參數不是函數 // 會被忽略,也就是promise穿透 else { value = detail; succeeded = true; } if (promise._state === PENDING) { if (hasCallback && succeeded || settled === FULFILLED) { resolve(promise, value); } else if (failed || settled === REJECTED) { reject(promise, error); } } }
例如以下例子,結果都是輸出foo:
Promise.resolve('foo').then(Promise.resolve('bar')).then(function (result) { console.log(result); }); Promise.resolve('foo').then(null).then(function (result) { console.log(result); });
promise可以很好的解決金字塔問題,可是有時候咱們也是須要適當使用金字塔的,例如咱們要同時獲取兩個promise的結果,可是這兩個promise是有關聯的,也就是有順序的,該怎麼辦?
也許解決方案會是這樣,定義一個全局變量,這樣在第二個then裏面就可使用兩個promise的結果了。
var user; getUserByName('nolan').then(function (result) { user = result; return getUserAccountById(user.id); }).then(function (userAccount) { // 好了, "user" 和 "userAccount" 都有了 });
可是這不是最好的方案,此時何不拋棄成見,擁抱金字塔:
getUserByName('nolan').then(function (user) { return getUserAccountById(user.id).then(function (userAccount) { // 好了, "user" 和 "userAccount" 都有了 }); });
promise是如此強大並且難以理解,可是抓住實質以後其實並無想象的那麼複雜,這也是爲何我要寫下這篇文章。更過關於如何正確使用promise,請看第三篇參考文章,強力推薦。