JS魔法堂:mmDeferred源碼剖析

1、前言                            html

  avalon.js的影響力愈發強勁,而做爲子模塊之一的mmDeferred必然成爲異步調用模式學習之旅的又一站呢!本文將記錄我對mmDeferred的認識,如有紕漏請各位指正,謝謝。項目請見:mmDeferred@githubgit

 

2、API說明                          github

   {Deferred} Deferred({Function|Object} mixin?) ,建立一個Deferred實例,當mixin的類型爲Object時,將mixin的屬性和函數附加到Deferred實例的Promise實例上。promise

    {String} state() ,獲取當前Deferred實例的狀態,含三種狀態:pending,fulfilled,rejected;轉換關係爲:pending->fulfilled,pending-rejected。數據結構

   {Promise} then({Function} resolvefn?, {Function} rejectfn?, {Function} notifyfn?, {Function} ensurefn?) ,向當前的Deferred實例添加四類回調函數,並返回一個新的Promise實例。其中resolvefn是實例狀態轉換爲fulfilled時調用,而rejectfn是實例狀態轉換爲rejected時調用,而notifyfn則至關於Promises/A+規範中的progressHandler同樣與實例狀態無關只需調用notify函數則調用notifyfn,ensurefn的做用爲模擬當前Deferred實例執行resolvefn、rejectfn和notifyfn的finally語句塊,不管執行前面三個函數的哪個均會執行ensurefn。閉包

    {Promise} otherwise({Function} rejectfn?) ,向當前的Deferred實例添加rejectfn回調函數,並返回一個新的Promise實例。app

    {Promise} ensure({Function} ensurefn?) ,向當前的Deferred實例添加ensurefn回調函數,並返回一個新的Promise實例。框架

    {undefined} resolve(...[*]) ,用於觸發fulfill回調——也就是觸發調用當前Deferred實例的resolvefn函數的請求,僅能調用一次。異步

    {undefined} reject(...[*]) ,用於觸發reject回調——也就是觸發調用當前Deferred實例的rejectfn函數的請求,僅能調用一次。函數

    {undefined} notify(...[*]) ,用於觸發notify回調——也就是觸發調用當前Deferred實例的notifyfn函數的請求,能調用屢次。

   {Promise} Deferred.all(...[Promise]) ,要求傳入多個Promise對象,當它們都正常觸發時,就執行它的resolve回調。至關於jQuery的when方法,但all更標準,是社區公認的函數。

     {Promise} Deferred.any(...[Promise]) ,要求傳入多個Promise對象,最早正常觸發的Promise對象,將執行它的resolve回調。

 

、源碼剖析                              

  首先要了解的是mmDeferred中存在Deferred和Promise兩個操做集合(二者操做同一個的數據結構實例),Promise用於向實例添加四類回調函數,而Deferred用於發起實例狀態變化或觸發回調函數調用的操做,而且限制爲僅經過Deferred函數返回的爲Deferred操做集合,而其餘API返回的均爲Promise操做集合。

  另外,值得注意的有如下幾點

  1. mmDeferred在實例狀態轉換的實現方式上是採起先調用回調函數再修改實例狀態的方式;

  2. resolve、reject等的實現上並非統一採用異步調用的方式在執行回調函數,而是當實例已經被添加了回調函數時同步執行回調函數,當未添加回調函數時則發起異步調用,讓當前執行的代碼塊有機會向實例添加回調函數;

  3. 不支持如下方式的回調函數晚綁定:

var deferred = Deferred()
deferred.resolve()
setTimeout(function(){
  deferred.promise.then(function(){
    console.log('hello world')
  })
}, 0)

  在代碼結構上值得注意的是

  1. 利用JS中變量聲明自動提高(hoist)的特性,經過前置return語句將對外接口與具體實現的代碼分離。

  2. 提取resolve、reject等函數的共性到私有函數_fire中,提供then、otherwise等函數的共性到私有函數_post中,提供Deferred.all和Deferred.any的共性到私有函數some中,避免重複代碼從而大大減小代碼量。

  待改進點我以爲應該將_fire和_post函數移出至Deferred函數以外,經過入參取代閉包引用外部變量的方式來獲取和修改實例屬性,那麼每次調用Deferred函數時就不會從新聲明新的_fire和_post函數了。

  存在疑惑的地方爲

    假設當前實例A狀態爲pending,那麼執行notify回調函數後當前實例A的狀態是不變的,當後續執行的ensure函數拋出異常,那麼將調用鏈表中下一個實例B的reject方法致使實例B的狀態爲rejected,但實例A狀態依然爲pending。這時再次調用實例B的resolve或reject方法均不會觸發執行相應的回調函數,但可經過調用實例A的resovle或reject方法執行實例A和實例B相應的回調函數。

  下面是源碼

define("mmDeferred", ["avalon"], function(avalon) {
    var noop = function() {
    }
    function Deferred(mixin) {
        var state = "pending"
            // 標識是否已經添加了回調函數
            , dirty = false
        function ok(x) {
            state = "fulfilled"
            return x
        }
        function ng(e) {
            state = "rejected"
            // 將異常日後傳遞
            throw e
        }
        // Deferred實例
        var dfd = {
            callback: {
                resolve: ok,
                reject: ng,
                notify: noop,
                ensure: noop
            },
            dirty: function() {
                return dirty
            },
            state: function() {
                return state
            },
            promise: {
                then: function() {
                    return _post.apply(null, arguments)
                },
                otherwise: function(onReject) {
                    return _post(0, onReject)
                },
                ensure: function(onEnsure) {
                    return _post(0, 0, 0, onEnsure)
                },
                _next: null
            }
        }
        if (typeof mixin === "function") {
            mixin(dfd.promise)
        } else if (mixin && typeof mixin === "object") {
            for (var i in mixin) {
                if (!dfd.promise[i]) {
                    dfd.promise[i] = mixin[i]
                }
            }
        }

        "resolve,reject,notify".replace(/\w+/g, function(method) {
            dfd[method] = function() {
                var that = this, args = arguments
                if (that.dirty()) {
                    // 若已經添加了回調函數,則立刻同步調用
                    _fire.call(that, method, args)
                } else {
                    // 若未添加回調函數,則發起異步調用,讓當前代碼塊的後續部分有足夠的時間添加回調函數
                    Deferred.nextTick(function() {
                        _fire.call(that, method, args)
                    })
                }
            }
        })

        return dfd

        /** 精彩之處:
         * 因爲JS會將變量聲明自動提高(hoist)到代碼塊的頭部
         * 所以這裏將私有方法寫在return語句以後從而更好地格式化代碼結構
         */

        // 添加回調函數到當前Deferred實例上
        function _post() {
            var index = -1, fns = arguments;
            "resolve,reject,notify,ensure".replace(/\w+/g, function(method) {
                var fn = fns[++index];
                if (typeof fn === "function") {
                    dirty = true
                    if (method === "resolve" || method === "reject") {
                        // 將修改Deferred實例狀態的功能封裝到回調函數中
                        // 也就是先調用回到函數再修改實例狀態
                        dfd.callback[method] = function() {
                            try {
                                var value = fn.apply(this, arguments)
                                state = "fulfilled"
                                return value
                            } catch (err) {
                                state = "rejected"
                                return err
                            }
                        }
                    } else {
                        dfd.callback[method] = fn;
                    }
                }
            })
            // 建立鏈表的下一個Deferred實例
            var deferred = dfd.promise._next = Deferred(mixin)
            return deferred.promise;
        }

        function _fire(method, array) {
            var next = "resolve", value
            if (this.state() === "pending" || method === "notify") {
                var fn = this.callback[method]
                try {
                    value = fn.apply(this, array);
                } catch (e) {//處理notify的異常
                    value = e
                }
                if (this.state() === "rejected") {
                    next = "reject"
                } else if (method === "notify") {
                    next = "notify"
                }
                array = [value]
            }
            var ensure = this.callback.ensure
            if (noop !== ensure) {
                try {
                    ensure.call(this)//模擬finally
                } catch (e) {
                    next = "reject";
                    array = [e];
                }
            }
            var nextDeferred = this.promise._next
            if (Deferred.isPromise(value)) {
                // 若是回調函數返回值爲Deferred實例,那麼就將該實例插入nextDeferred以前
                value._next = nextDeferred
            } else {
                if (nextDeferred) {
                    _fire.call(nextDeferred, next, array);
                }
            }
        }
    }

    window.Deferred = Deferred;
    Deferred.isPromise = function(obj) {
        return !!(obj && typeof obj.then === "function");
    };

    function some(any, promises) {
        var deferred = Deferred(), n = 0, result = [], end
        function loop(promise, index) {
            promise.then(function(ret) {
                if (!end) {
                    result[index] = ret//保證回調的順序
                    n++;
                    if (any || n >= promises.length) {
                        deferred.resolve(any ? ret : result);
                        end = true
                    }
                }
            }, function(e) {
                end = true
                deferred.reject(e);
            })
        }
        for (var i = 0, l = promises.length; i < l; i++) {
            loop(promises[i], i)
        }
        return deferred.promise;
    }
    Deferred.all = function() {
        return some(false, arguments)
    }
    Deferred.any = function() {
        return some(true, arguments)
    }
    Deferred.nextTick = avalon.nextTick
    return Deferred
})

 

4、總結                            

  源碼中還提供了相關資料的連接,可讓咱們更瞭解Promise/A+規範哦!

  尊重原創,轉載請註明來自:http://www.cnblogs.com/fsjohnhuang/p/4162646.html 肥子John

 

5、參考                            

《JavaScript框架設計》

相關文章
相關標籤/搜索