build your promise step by step

最近看了一篇關於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);
            }
        }
    }

Defining the Promise type

首先來看一段定義簡單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函數中的callbackvalue進行處理前被賦值了。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。一樣不能正常的執行。

別急,慢慢來。

Promises have state

事實上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的值進行流程操做:

  • 若是statepending狀態,即在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

可是這樣的Promise構造函數仍是有問題的,你們能夠想象下,在調用resolve()方法前,調用了不少次的then()方法,那麼只有最後一個then()方法裏面注入的callback纔會有用。解決這個問題的方法就是維持一個deferreds隊列,去保存每次then()方法注入的回調函數。

Chaining Promises

下面的代碼是最普通不過的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個屬性,一個爲onResolvedthen()方法裏面注入的回調函數,用來對傳入的上一個promise傳遞過來的值進行處理;另外一個爲resolvePromise構造函數內部定義的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以及做爲傳入下一個promisevalue:

if(!handler.onResolved) {
        handler.resolve(value);
        return;
    }
再每次調用then()方法的過程都會新建一個pending狀態的promise,並經過resolve方法改變狀態,若是then()方法中注入了回調函數,並返回了值,那麼這個值會一直傳遞下去,若是沒有注入回調函數,resolve方法會獲取上一個promise傳遞過來的值,並做爲傳入下一個promise的值。即then()方法注入的回調函數是可選的。

經過再次對Promise構造函數的增強,完成了promise鏈式調用的功能。


對於reject的部分過2天加上。

相關文章
相關標籤/搜索