Promise介紹--Deferred及jQuery篇

最近懶癌發做,說好的系列文章,寫了一半,一直懶得寫,今天補上一篇。segmentfault

Deferred

咱們在使用promise對象時,總會提到一個與它關係密切的對象——Deferred。其實Deferred沒什麼內容可講的,其實很簡單。數組

  • 它包含一個promise對象promise

  • 它能夠改變對應的promise的狀態app

簡單的實現以下:ide

class Deferred{
    constructor(){
        let defer = {};
        defer.promise = new Promise((resolve, reject)=>{
            defer.resolve = resolve;
            defer.reject = reject;
        })
        return defer;
    }
}

咱們知道promise對象內部的狀態,自己是在建立對象時傳入的函數內控制,外部是訪問不到的,Deferred對象在它的基礎上包裝了一層,並提供了兩個在外部改變它狀態的方法。函數

用法其實在Promise介紹--規範篇中的例子內,多處使用到了,這裏就再也不贅述。總以爲文章寫這麼點兒,就顯得太水了。。因此藉此,講講jQuery中的實現。this

jQuery中還有一個靜態方法$.Callbacks(),因爲$.Deferred()強依賴它,因此咱們先從它開刀。spa

$.Callbacks()

$.Callbacks()咱們稱爲回調對象,它內部會維護一個數組,咱們能夠向其中添加若干個回調函數,而後在某一條件下觸發執行。code

圖片描述

有幾個方法從名字咱們就知道它的做用是什麼,add向數組內部添加一個回調函數,empty清空數組,fire觸發回調函數,has數組中是否已經添加某回調函數,remove從數組中刪除某回調函數。對象

fireWith函數接收兩個參數,第一個是回調函數執行的上下文,第二個是回傳給回調函數的參數。fire中其實內部調用的就是fireWith,其中第一個參數傳遞的是this

其它的幾個函數,都和回調數組的狀態有關。建立Callbacks對象時,接收一個字符串或者對象做爲參數。其實內部都會轉換爲對象,這裏不贅述,不一樣字符串表示不一樣的處理方式,一一介紹。

once :對象只會調用一次。

let cb = $.Callbacks('once')
function a(){console.log('a')}
function b(){console.log('b')}
cb.add(a)
cb.fire()
cb.add(b)
cb.fire()
// a

第一次fire以後,回調列表以後不會再次觸發。

memory : 記住回調列表的執行狀態,若是回調函數fire過一次,以後每次add以後,則自動觸發該回調。

let cb = $.Callbacks('memory')
function a(){console.log('a')}
function b(){console.log('b')}
cb.add(a)
cb.fire()
// a
cb.add(b)
// b

第一次fire以後,再次add新的回調函數b時,自動執行回調b

unique:每個回調函數只能夠添加一次。

let cb = $.Callbacks('unique')
function a(){console.log('a')}
function b(){console.log('b')}
cb.add(a)
cb.add(a)
cb.fire()
// a
cb.add(b)
cb.fire()
// a
// b

第一次fire時,只會打印一個a,說明第二個a沒有添加成功,但當咱們添加b時,是能夠添加成功的。

stopOnFalse:當前面的回調函數返回false時,終止後面的回調繼續執行。

let cb = $.Callbacks('stopOnFalse')
function a(){console.log('a');return false;}
function b(){console.log('b')}
cb.add(a)
cb.add(b)
cb.fire()
// a

函數a返回了false,致使函數b沒有執行。

咱們再回過頭看$.Callbacks()對象的方法,lock方法表示鎖住回調數組,再也不執行,也就是模式爲once時,調用一次fire後的狀態,即在此以後不能夠在此觸發。以下:

let cb = $.Callbacks()
function a(){console.log('a')}
function b(){console.log('b')}
cb.add(a)
cb.fire()
cb.lock()
cb.add(b)
cb.fire()

lock以後,再次添加函數b並調用fire時,不會再次執行,與once模式下效果相似。但若是是memory模式,回調先fire,而後再lock,以後再次add時,新添加的函數依然會執行。

let cb = $.Callbacks('memory')
function a(){console.log('a')}
function b(){console.log('b')}
cb.add(a)
cb.fire()
cb.lock()
cb.add(b)
// a
// b

其實這種效果和直接建立回調對象時,參數設爲once memory是一致的。也就是說,以下代碼與上面效果一致。

let cb = $.Callbacks('once memory')
function a(){console.log('a')}
function b(){console.log('b')}
cb.add(a)
cb.fire()
cb.add(b)
// a
// b

咱們發現這種once memory模式,正好與Promisethen方法添加的回調很相似。若是promise對象處於pending狀態,則then方法添加的回調存儲在一個數組中,當promise對象狀態改變(fire)時,執行相應的回調,且以後再次經過then方法添加回調函數,新回調會馬上執行。同時,每個回調只能執行一次。因此,$.Deferred()內部用的正好是once memoryCallbacks

還有一個函數叫作disable,它的做用是直接禁用掉這個回調對象,清空回調數組,禁掉fireadd等。

locked用於判斷數組是否被鎖住,返回truefalsedisabled用於判斷回調對象是否被警用,一樣返回truefalse

$.Deferred()

有了以上的基礎,咱們接下來看看jQuery中,Deferred對象的實現。咱們先看看它都有哪些方法。如圖:

圖片描述

別的方法咱們暫且不關注,咱們注意到裏面有四個咱們比較熟悉的方法,promiserejectresolve。它們和咱們前面說的Deferred對象中做用差很少。

jQueryDeferred中有三個添加回調函數的方法donefailprogress,分別對應添加promise狀態爲resolvedrejectedpending時的回調,同時對應有三個觸發回調函數的方法resolverejectnotify

咱們接下來看看它內部是怎麼實現的。首先爲每種狀態分別建立一個Callbacks對象,以下:

var tuples = [
      // action, add listener, listener list, final state
     ["resolve", "done", jQuery.Callbacks("once memory"), "resolved"],
     ["reject", "fail", jQuery.Callbacks("once memory"), "rejected"],
     ["notify", "progress", jQuery.Callbacks("memory")]
]

咱們發現donefail對應的回調對象是once memory,而progress對應的是memory。說明經過progress添加的函數,能夠屢次重複調用。

而後定義了一個state用來保存狀態,以及內部的一個promise對象。

state = "pending",
promise = {
    state: function() {
        return state;
    },
    always: function() {
        deferred.done(arguments).fail(arguments);
        return this;
    },
    then: function( /* fnDone, fnFail, fnProgress */ ) {
    },

    // Get a promise for this deferred
    // If obj is provided, the promise aspect is added to the object
    promise: function(obj) {
        return obj != null ? jQuery.extend(obj, promise) : promise;
    }
},

接下來會執行一個循環,以下:

// Add list-specific methods
jQuery.each(tuples, function(i, tuple) {
    var list = tuple[2],
        stateString = tuple[3];

    // promise[ done | fail | progress ] = list.add
    promise[tuple[1]] = list.add;

    // Handle state
    if (stateString) {
        list.add(function() {

            // state = [ resolved | rejected ]
            state = stateString;

            // [ reject_list | resolve_list ].disable; progress_list.lock
        }, tuples[i ^ 1][4].disable, tuples[2][5].lock);
    }

    // deferred[ resolve | reject | notify ]
    deferred[tuple[0]] = function() {
        deferred[tuple[0] + "With"](this === deferred ? promise : this, arguments);
        return this;
    };
    deferred[tuple[0] + "With"] = list.fireWith;
});

這一段代碼中咱們能夠看出,donefailprogress其實就是addresolverejectnotify其實就是fire,與之對應的resolveWithrejectWithnotifyWith其實就是fireWith。且成功和失敗的回調數組中,會預先添加一個函數,用來設置promise的狀態和禁用掉其它狀態下的回調對象。

從上面這段代碼中咱們也能夠看出,添加回調函數的方法,都是添加在promise對象上的,而觸發回調的方法是添加在deferred對象上的。代碼中會經過以下方法,把promise對象的方法合併到deferred對象上。

promise.promise(deferred);

因此,咱們打印一下$.Deferred().promise()

圖片描述

發現它確實比$.Deferred()少了那幾個觸發回調的方法。

其它的幾個方法咱們簡單說一下,always會同時在成功和失敗的回調數組中添加方法。state是查看當前promise對象的狀態。

then方法以下:

then: function( /* fnDone, fnFail, fnProgress */ ) {
    var fns = arguments;
    return jQuery.Deferred(function(newDefer) {
        jQuery.each(tuples, function(i, tuple) {
            var fn = jQuery.isFunction(fns[i]) && fns[i];

            // deferred[ done | fail | progress ] for forwarding actions to newDefer
            deferred[tuple[1]](function() {
                var returned = fn && fn.apply(this, arguments);
                if (returned && jQuery.isFunction(returned.promise)) {
                    returned.promise()
                        .progress(newDefer.notify)
                        .done(newDefer.resolve)
                        .fail(newDefer.reject);
                } else {
                    newDefer[tuple[0] + "With"](
                        this === promise ? newDefer.promise() : this,
                        fn ? [returned] : arguments
                    );
                }
            });
        });
        fns = null;
    }).promise();
}

從代碼中咱們能夠看出,then方法和規範中的then方法相似,不過這裏多了第三個參數,是用於給progress添加回調函數,同時返回一個新的promise對象。

pipe是爲了向前兼容,它與then是相等的。

$.when()

jQuery中還有一個與之相關的方法$.when(),它的做用相似於Promise.all()。具體實現方式基本是新建了一個Deferred對象,而後遍歷全部傳遞進去的promise對象。不過添加了progres的處理。總之和規範有不少的不一樣,你們有興趣的就本身看一下吧。

相關文章
相關標籤/搜索