Javascript異步編程-延遲對象篇

上篇文章中講到,使用jquery的ajax方法操做ajax請求,會受到回調函數嵌套的問題。固然,jquery團隊也發現了這個問題,在2011年,也就是jquery 1.5版本以後,jQuery.Deferred對象爲解決這類問題應運而出。以後,zapto等框架也推出相同api的deferred對象,來進行異步操做。javascript

在jquery 1.5 版本以後,ajax請求的內部實現被重寫。$.ajax方法返回的再也不是一個jqXHR對象,而是一個Deferred對象。java

使用jquery 1.5版本以後的代碼,能夠用下面的方法進行一次ajax請求。jquery

// 前提引入jquery
var fetchData = function (url) {
    return $.ajax({
        type: 'get',
        url: url
    });
}

這樣一次請求的內容就已經完成,$.ajax返回一個$.Deferred對象,那麼咱們就可使用$.Deferred對象的api進行一些異步操做。git

出於篇幅,這裏只簡單進行介紹.done.fail.then$.when這三個方法。github

對於每個$.Deferred對象來講,實例有多個方法,其中done方法表明異步完成時執行的方法,fail表明異步失敗時執行的方法,這兩個方法同時仍舊返回一個$.Deferred對象的實例。ajax

繼續上面的ajax操做,咱們能夠這樣寫成功和失敗的回調:segmentfault

// fetchData 接上

fetchData()        //執行函數返回一個Deferred對象實例
    .done()        //接受一個函數,ajax請求成功調用
    .fail()        //接受一個函數,ajax請求失敗調用
    .done()        //第二個成功狀態的回調方法
    .fail()

一樣的對於.then方法,接受兩個函數,第一個表明成功時執行的方法,第二個表明失敗時的執行方法。一樣的,它也返回一個deferred對象實例。意味着也能進行連綴調用。api

fetchData()
    .then(successFn, errorFn)        //第一次回調
    .then(successFn, errorFn)        //第二次回調

內部實現上,.done 和 .fail 都是基於 .then實現的數組

fetchData()                            fetchData()
    .done(successFn)    <===>            .then(successFn, null)
    .fail(errorFn)      <===>            .then(null, errorFn)

對於多個ajax同時請求,共同執行同一個回調函數這一點上,jquery有一個$.when方法,接受多個Deferred對象實例,同時執行。框架

var fetchData = function (url) {
    return $.ajax({
        type: 'get',
        url: url
    });
}

var fetchData1 = fetchData('/path/to/data1');
var fetchData2 = fetchData('/path/to/data2');

$.when(fetchData1, fetchData2, function (data1, data2) {
    // fetchData1 響應爲data1
    // fetchData2 響應爲data2
})

完美解決了開發中的異步問題。

上面的$.ajax只是在$.deferred對象上封裝了一層ajax操做。實際上,真正的$.Deferred對象是這樣調用的:

function printA () {
    var deferred = new $.Deferred();
    setTimeout(function () {
        console.log('A');
        deferred.resolve(' is done.');
    }, 1000);
    return deferred;
}

function printB (msg) {
    var deferred = new $.Deferred();
    setTimeout(function () {
        console.log('B' + msg);
        deferred.resolve();
    }, 1000);
    return deferred;
}

printA()
    .then(printA)
    .then(printB)

每一個函數維護一個Deferred對象,在每個具備異步操做的函數執行成功後,指示全局deferred對象執行下一個函數,達到異步的效果。

新建完成$.Deferred實例deferred以後,調用deferred.resolve()表明成功完成響應,deferred.reject()即表明調用失敗響應。

詳細解釋源碼請參見另外一篇文章,這裏咱們主要寫一下這種調用方式實現的tiny版。

首先咱們寫一個Callback對象維護咱們的回調函數隊列

var Callbacks = function () {
    function Callbacks () {
        this.callbacks = [];
    }
    Callbacks.prototype.add = function (fn) {
        this.callbacks.add(fn);
        return this;
    }
    Callbacks.prototype.fire = function () {
        var len = this.callbacks.length;
        if(len) {
            this.callbacks.unshift()();
        }
    }
    return Callbacks;
}

這段代碼邏輯很簡單,Callbacks對象有兩個方法,分別是add和fire,調用add則向當前的callbacks數組內新增一個function。fire方法,則從Callbacks中提取最前的一個callback,並執行它。

對於Deferred對象,咱們至少須要resolve和reject兩個方法。進行成功和失敗的調用。而且可以進行鏈式調用。

var Deferred = function () {
    this.successCbs = new Callbacks();
    this.errorCbs = new Callbacks();
}
Deferred.prototype.then = function (successCb, errorCb) {
    this.successCbs.add(successCb);
    this.errorCbs.add(errorCb);
    return this;
}
Deferred.prototype.resolve = function () {
    this.successCbs.fire();
    return this;
}
Deferred.prototype.reject = function () {
    this.errorCbs.fire();
    return this;
}

這樣簡單完成以後,咱們新建一個Deferred實例,就可以經過鏈式調用的方式進行異步操做。

var deferred = new Deferred();
function printA() {
    setTimeout(function () {
        console.log('A');
        deferred.resolve();
    }, 1000);
    return deferred;
}
function printB() {
    setTimeout(function () {
        console.log('B');
        deferred.resolve();
    }, 1000);
}

printA()
    .then(printB)
    .then(printA)

一樣的,咱們能夠封裝一個自制tiny-Deferred對象的tiny-ajax方法。

var ajax = function (options) {
    var xhrOptions = {
        type: options.type || 'get',
        url: options.url || '/default/path',
        async: options.async || true
    };
    var deferred = new Deferred();
    var xhr = new XHRHttpRequest();
    xhr.open(xhrOptions.type, xhrOptions.url, xhrOptions.async);
    xhr.onload = function (result) {
        deferred.resolve(result);
    }
    xhr.onerror = function ()
    xhr.send();
    return deferred;
}

具體源代碼開源在Github上,歡迎pr和issuses。

相關文章
相關標籤/搜索