Javascript 採用回調函數(callback)來處理異步編程。從同步編程到異步回調編程有一個適應的過程,可是若是出現多層回調嵌套,也就是咱們常說的厄運的回調金字塔(Pyramid of Doom),絕對是一種糟糕的編程體驗。因而便有了 CommonJS 的 Promises/A 規範,用於解決回調金字塔問題。本文先介紹 Promises 相關規範,而後再經過解讀一個迷你的 Promises 以加深理解。javascript
一個 Promise 對象表明一個目前還不可用,可是在將來的某個時間點能夠被解析的值。它容許你以一種同步的方式編寫異步代碼。例如,若是你想要使用 Promise API 異步調用一個遠程的服務器,你須要建立一個表明數據將會在將來由 Web 服務返回的 Promise 對象。惟一的問題是目前數據還不可用。當請求完成並從服務器返回時數據將變爲可用數據。在此期間, Promise 對象將扮演一個真實數據的代理角色。接下來,你能夠在 Promise 對象上綁定一個回調函數,一旦真實數據變得可用這個回調函數將會被調用。java
Promise 對象曾經以多種形式存在於許多語言中。git
Javascript 中最多見的反模式作法是回調內部再嵌套回調。github
// 回調金字塔 asyncOperation(function(data){ // 處理 `data` anotherAsync(function(data2){ // 處理 `data2` yetAnotherAsync(function(){ // 完成 }); }); });
引入 Promises 以後的代碼算法
promiseSomething() .then(function(data){ // 處理 `data` return anotherAsync(); }) .then(function(data2){ // 處理 `data2` return yetAnotherAsync(); }) .then(function(){ // 完成 });
Promises 將嵌套的 callback ,改形成一系列的.then
的連綴調用,去除了層層縮進的糟糕代碼風格。 Promises 不是一種解決具體問題的算法,而已一種更好的代碼組織模式。接受新的組織模式同時,也逐漸以全新的視角來理解異步調用。編程
各個語言平臺都有相應的 Promise 實現promise
下面我來相信瞭解一下 javascript 語言環境下各個規範的一些細節。服務器
promise 表示一個最終值,該值由一個操做完成時返回。異步
完成
,或者未完成轉換成失敗 。promise 有一個 then 方法, then 方法能夠接受 3 個函數做爲參數。前兩個函數對應 promise 的兩種狀態 fulfilled 和 rejected 的回調函數。第三個函數用於處理進度信息(對進度回調的支持是可選的)。async
promiseSomething().then(function(fulfilled){ //當 promise 狀態變成 fulfilled 時,調用此函數 },function(rejected){ //當 promise 狀態變成 rejected 時,調用此函數 },function(progress){ //當返回進度信息時,調用此函數 });
若是 promise 支持以下連個附加方法,稱之爲可交互的 promise
在 Promises/A 的基礎上, Promises/B 定義了一組 promise 模塊須要實現的 API
when(value, callback, errback_opt)
若是 value 不是一個 promise ,那麼下一事件循環 callback 會被調用, value 做爲 callback 的傳入值。若是 value 是一個 promise , promise 的狀態已經完成或者變成完成時,那麼下一事件循環 callback 會被調用, resolve 的值會被傳入 callback ; promise 的狀態已經失敗或者變成失敗時,那麼下一事件循環 errback 會被調用, reason 會做爲失敗的理由傳入 errback 。
asap(value, callback, errback_opt)
與 when 最大的區別,若是 value 不是一個 promise ,會被當即執行,不會等到下一事件循環。
enqueue(task Function)
儘量快地在接下來的事件循環調用 task 方法。
get(object, name)
返回一個得到對象屬性的 promise 。
post(object, name, args)
返回一個調用對象方法的 promise 。
put(object, name, value)
返回一個修改對象屬性的 promise 。
del(object, name)
返回一個刪除對象屬性的 promise 。
makePromise(descriptor Object, fallback Function)
返回一個 promise 對象,該對象必須是一個可調用的函數,也多是可被實例化的構造函數。
{ "when": function(errback){...}, "get": function(name){...}, "put": function(name, value){...}, "post": function(name, args){...}, "del": function(name){...}, }
上面每個註冊的 handle 都返回一個 resolved value 或者 promise 。
defer()
返回一個對象,該對象包含一個 resolve(value) 方法和一個 promise 屬性。 當 resolve(value) 方法被第一次調用時, promise 屬性的狀態變成 完成,全部以前或以後觀察該 promise 的 promise 的狀態都被轉變成 完成。 value 參數若是不是一個 promise ,會被包裝成一個 promise 的 ref 。 resolve 方法會忽略以後的全部調用。
reject(reason String)
返回一個被標記爲 失敗 的 promise 。 一個失敗的 promise 上被調用 when(message) 方法時,會採用以下兩種方法之一
ref(value)
若是 value 是 promise 對象,返回 value 自己。不然,返回一個 resolved 的 promise ,攜帶以下 handle 。
isPromise(value) Boolean
判斷一個對象是不是 promise
method(name String)
得到一個返回 name 對應方法的 promise 。返回值是 「get」, 「put」, 「del」 和 「post」 對應的方法,可是會在下一事件循環返回。
爲了增長不一樣 promise 實現之間的可互操做性, Promises/D 規範對 promise 對象和 Promises/B 規範作了進一步的約定。以達到鴨子類型的效果( Duck-type Promise )。
簡單來講 Promises/D 規範,作了兩件事情,
Promise 對象必須是實現 promiseSend
方法。
promiseSend
方法就能夠甄別爲 promise 對象promiseSend
方法必須接受一個操做名稱,做爲第一個參數when
,此時第三個參數必須是 rejection 回調。
get
,此時第三個參數爲屬性名(字符串類型)put
,此時第三個參數爲屬性名(字符串類型),第四個參數爲新屬性值。del
,此時第三個參數爲屬性名post
,此時第三個參數爲方法的屬性名,接下來的變參爲方法的調用參數isDef
promiseSend
方法的第二個參數爲 resolver 方法promiseSend
方法可能接受變參promiseSend
方法必須返回undefined
Promises/D 規範中對 Promises/B 規範中定義的 ref 、 reject 、 def 、 defer 方法作了進一步細緻的約束,此處略去這些細節。
前面提到的 Promises/A/B/D 規範都是有 CommonJS 組織提出的, Promises/A+是有一個自稱爲Promises/A+ 組織發佈的,該規範是以 Promises/A 做爲基礎進行補充和修訂,旨在提升 promise 實現之間的可互操做性。
Promises/A+ 對.then
方法進行細緻的補充,定義了細緻的Promise Resolution Procedure流程,而且將.then
方法做爲 promise 的對象甄別方法。
此外, Promises/A+ 還提供了兼容性測試工具,以肯定各個實現的兼容性。
上面扯了這麼多規範,如今咱們看看如何實現一個簡單而短小的 Promise 。
var PENDING = 0; var FULFILLED = 1; var REJECTED = 2; function Promise() { // store state which can be PENDING, FULFILLED or REJECTED var state = PENDING; // store value or error once FULFILLED or REJECTED var value = null; // store sucess & failure handlers attached by calling .then or .done var handlers = []; }
僅支持兩種狀態變遷, fulfill 和 reject
// ... function Promise() { // ... function fulfill(result) { state = FULFILLED; value = result; } function reject(error) { state = REJECTED; value = error; } }
fulfill 和 reject 方法較爲底層,一般更高級的 resolve 方法開放給外部。
// ... function Promise() { // ... function resolve(result) { try { var then = getThen(result); if (then) { doResolve(then.bind(result), resolve, reject) return } fulfill(result); } catch (e) { reject(e); } } }
resolve 方法能夠接受一個普通值或者另外一個 promise 做爲參數,若是接受一個 promise 做爲參數,等待其完成。 promise 不容許被另外一個 promise fulfill ,因此須要開放 resolve 方法。 resolve 方法依賴一些幫助方法定義以下:
/** * Check if a value is a Promise and, if it is, * return the `then` method of that promise. * * @param {Promise|Any} value * @return {Function|Null} */ function getThen(value) { var t = typeof value; if (value && (t === 'object' || t === 'function')) { var then = value.then; if (typeof then === 'function') { return then; } } return null; } /** * Take a potentially misbehaving resolver function and make sure * onFulfilled and onRejected are only called once. * * Makes no guarantees about asynchrony. * * @param {Function} fn A resolver function that may not be trusted * @param {Function} onFulfilled * @param {Function} onRejected */ function doResolve(fn, onFulfilled, onRejected) { var done = false; try { fn(function (value) { if (done) return done = true onFulfilled(value) }, function (reason) { if (done) return done = true onRejected(reason) }) } catch (ex) { if (done) return done = true onRejected(ex) } }
這裏 resolve 和 doResolve 之間的遞歸很巧妙,用來處理 promise 的層層嵌套( promise 的 value 是一個 promise )。
// ... function Promise(fn) { // ... doResolve(fn, resolve, reject); }
// ... function Promise(fn) { // ... function handle(handler) { if (state === PENDING) { handlers.push(handler); } else { if (state === FULFILLED && typeof handler.onFulfilled === 'function') { handler.onFulfilled(value); } if (state === REJECTED && typeof handler.onRejected === 'function') { handler.onRejected(value); } } } this.done = function (onFulfilled, onRejected) { // ensure we are always asynchronous setTimeout(function () { handle({ onFulfilled: onFulfilled, onRejected: onRejected }); }, 0); } // ... }
// ... function Promise(fn) { // ... this.then = function (onFulfilled, onRejected) { var self = this; return new Promise(function (resolve, reject) { return self.done(function (result) { if (typeof onFulfilled === 'function') { try { return resolve(onFulfilled(result)); } catch (ex) { return reject(ex); } } else { return resolve(result); } }, function (error) { if (typeof onRejected === 'function') { try { return resolve(onRejected(error)); } catch (ex) { return reject(ex); } } else { return reject(error); } }); }); } // ... }
jQuery 1.8 以前的版本, jQuery 的 then 方法只是一種能夠同時調用 done 、 fail 和 progress 這三種回調的速寫方法,而 Promises/A 規範的 then 在行爲上更像是 jQuery 的 pipe 。 jQuery 1.8 修正了這個問題,使 then 成爲 pipe 的同義詞。不過,因爲向後兼容的問題, jQuery 的 Promise 再如何對 Promises/A 示好也不太會招人待見。
此外,在 Promises/A 規範中,由 then 方法生成的 Promise 對象是已執行仍是已拒絕,取決於由 then 方法調用的那個回調是返回值仍是拋出錯誤。在 JQuery 的 Promise 對象的回調中拋出錯誤是個糟糕的主意,由於錯誤不會被捕獲。
最後一個例子揭示了,實現 Promise 的關鍵是實現好 doResolve 方法,在完事之後觸發回調。而爲了保證異步 setTimeout(fun, 0);
是關鍵一步。
Promise 一直用得蠻順手的,其很好的優化了 NodeJS 異步處理時的代碼結構。可是對於其工做原理卻有些懵懂和好奇。因而花了些經理查閱並翻譯了 Promise 的規範,以充分的理解 Promise 的細節。