最近看了一篇關於Promise
內部實現原理的文章Javascript in wicked detail。做者從簡明的例子入手,一步一步的構建健壯的Promise
實現。我就拿做者文中的代碼實例梳理下文章的核心內容。javascript
你們必定看到過嵌套很深回調函數,那麼如何在保證代碼流程才能將這些縱向嵌套的代碼變成橫向偏平的呢?java
doSomething(function(value) { console.log('Got a value:' + value); })
to thispromise
doSomething().then(function(value) { console.log('Got a value:' + value); })
那麼咱們就應該在定義doSomething
函數的時候作出相應的變化異步
function doSomething(callback) { var value = 42; callback(value); }
to this函數
function doSomething() { return { then: function(callback) { var value = 42; callback(42); } } }
首先來看一段定義簡單Promise
構造函數的代碼:this
function Promise(fn) { var callback = null; this.then = function(cb) { callback = cb; } function resolve(value) { callback(value) } fn(resolve); }
而後重寫doSomething()
函數:code
function doSomething() { return new Promise(function(resolve) { var value = 42; resolve(value); }) }
從新定義後的doSomething()
函數執行後返回獲得一個promise實例
,實例上有then()
方法,能夠接受回調函數。對象
doSomething().then(function(value) { console.log(value); })
可是上面的代碼會報錯(callback is undefined)
,是由於:resolve中的callback要早於then()方法中的callback的賦值操做。隊列
那麼對Promise
構造函數稍微處理下,把同步的代碼使用setTimeout
來hack下,改變代碼的執行順序,使得resolve
函數中的callback
對value
進行處理前被賦值了。ip
function Promise(fn) { var callback = null; this.then = function(cb) { callback = cb; } function resolve(value) { setTimeout(function() { callback(value); }, 1) } fn(resolve); }
這裏經過setTimeout
異步函數改變了代碼執行的順序,確保callback
被調用前已經被賦值成cb
。
從新調用:
doSomething().then(function(value) { console.log(value); }) // 42 //正常執行。
可是定義Promise
構造函數的代碼仍是有問題的,由於若是僅僅是調用then()
方法而注入回調的話,內部的callback
仍然是null
。一樣不能正常的執行。
別急,慢慢來。
事實上Promise
是有狀態的:
pending
resolved
rejected
pending => resolved
或者 pending => rejected
。狀態一旦發生改變,不可逆。接下來,讓咱們在Promise
的構造函數裏面加入state
,使用state
來控制整個代碼流程。
function Promise(fn) { var state = 'pending', value, deferred; function resolve(newValue) { state = 'resolved'; value = newValue; if(deferred) { handle(deferred); } } function handle(onResolved) { if(state === 'pending') { deferred = onResolved; return ; } onResolved(value); } this.then = function(onResolved) { handle(onResolved); } fn(resolve); }
代碼變的比以前更加複雜。可是如今使用state
來控制代碼的流程。then()方法
和resolve()方法
將控制權交給了新的方法handle()
,由handle()
方法來根據state
的值進行流程操做:
若是state
爲pending
狀態,即在resolve()
以前調用了then()
方法,那麼會將onResolved
回調賦值給一個deferred延遲對象
,deferred對象
將這個回調保存起來,稍後當resolve()
調用時,pending
狀態變爲resolved
,並調用deferred對象
。
若是在then()
方法前調用resolve()
方法,pending
狀態變爲resolved
,而後調用then()
裏面注入的回調onResolved
.
經過以上的代碼,promise
能夠任意次數的調用then()
方法:
var promise = doSomething(); promise.then(function(value) { console.log('Got a value:', value); }); // 42 promise.then(function(value) { console.log('Got the some value again:', value); }); //42
可是這樣的Promis
e構造函數仍是有問題的,你們能夠想象下,在調用resolve()
方法前,調用了不少次的then()
方法,那麼只有最後一個then()
方法裏面注入的callback
纔會有用。解決這個問題的方法就是維持一個deferreds隊列
,去保存每次then()
方法注入的回調函數。
下面的代碼是最普通不過的promise鏈式調用:
getSomeData() .then(filterTheData) .then(processTheData) .then(displayTheData)
getSomeData()
方法調用後會返回一個promise對象
,這樣即可以調用then()
方法,一樣這第一個then()
方法調用後也會返回一個promise
對象。這樣才能繼續調用then()
方法。
then()方法老是返回一個promise。
接下來在代碼中加以實現:
function Promise(fn) { var state = 'pending', value, deferred = null; function resolve(newValue) { state = 'resolved'; value = newValue; if(deferred) { handle(deferred); } } function handle(handler) { if(state == 'pending') { deferred = handler; return; } if(!handler.onResolved) { handler.resolve(value); return; } var ret = handler.onResolved(value); handler.resolve(ret); } this.then = function(onResolved) { return new Promise(function(resolve) { handle({ onResolved: onResolved, resolve: resolve }); }); }; fn(resolve); }
在此次的代碼中,調用then()
方法後會返回一個新的生成的promise對象
。它具備then()
方法,能夠繼續調用then()
,並返回一個新生成的promise
對象。如此繼續進行下去。這就實現了Promise
鏈式調用。
再來看看具體的代碼實現:resolve()
方法沒什麼變化,可是handle()
方法接收一個handler
對象。handler
對象有2個屬性,一個爲onResolved
,then()
方法裏面注入的回調函數,用來對傳入的上一個promise
傳遞過來的值進行處理;另外一個爲resolve
,Promise
構造函數內部定義的resolve()
方法,用來改變Promise
狀態以及value
值。
具體分析下handle()
函數:
function handle(handler) { if(state === 'pending') { deferred = handler; return; } if(!handler.onResolved) { handler.resolve(value); return; } var ret = handler.onResolved(value); handler.resolve(ret); }
每次調用then()
方法新建一個promise
對象過程中,handle({onResolved: onResolved, resolve: resolve})
中resolve
屬性始終是得到的定義過程當中對外部resolve
方法的引用。即上一次的promise
中定義的resolve
.
當then()方法裏面注入回調函數時,調用onResolved方法並得到返回值ret,傳入resolve方法,改變state的值以及更改promise中須要繼續傳遞下去的值。若是onResolved方法中會返回處理過的值,那麼下一個promise能拿到這個值,若是onResolved沒有返回,傳入下一個promise的爲undefined**
doSomething().then(function(result) { console.log('First result', result); return 88; }).then(function(secondResult) { console.log('second result', secondResult); }) //the output is // //First result 42 //Second result 88 doSomething().then(function(result) { console.log('First result', result); }).then(function(secondResult) { console.log('Second result', secondResult); }) //now the output is //First result 42 //Second result undefined
當then()沒有注入回調函數時,仍然會調用resolve方法,改變state的值,以及獲取上一個promise傳遞過來的值,並將值傳遞給下一個promise。
doSomething().then().then(function(result) { console.log('Got a result', result); }); //the output is // //Got a result 42
主要是得益於handle()
方法中,調用resolve
方法獲取從上一個promise
獲得的value
以及做爲傳入下一個promise
的value
:
if(!handler.onResolved) { handler.resolve(value); return; }
再每次調用then()方法的過程都會新建一個pending狀態的promise,並經過resolve方法改變狀態,若是then()方法中注入了回調函數,並返回了值,那麼這個值會一直傳遞下去,若是沒有注入回調函數,resolve方法會獲取上一個promise傳遞過來的值,並做爲傳入下一個promise的值。即then()方法注入的回調函數是可選的。
經過再次對Promise構造函數
的增強,完成了promise鏈式調用
的功能。
對於reject的部分過2天加上。