通過(guò) ES6 Promise 和 jQuery Deferred 的異同學(xué)習(xí) Promise

2018-06-09 10:44 更新

Deferred 和 Promise

ES6 和 jQuery 都有 Deffered 和 Promise,但是略有不同。不過(guò)它們的作用可以簡(jiǎn)單的用兩句話來(lái)描述

  • Deffered 觸發(fā) resolve 或 reject

  • Promise 中申明 resolve 或 reject 后應(yīng)該做什么(回調(diào))

在 jQuery 中

var deferred = $.Deferred();
var promise = deferred.promise();

在 ES6 中

var deferred = Promise.defer();
var promise= defered.promise;

MDN 宣布 Deferred 在 Gecko 30 中被申明為過(guò)期,不應(yīng)該再使用,而應(yīng)該用 new Promise() 來(lái)代替。關(guān)于 new Promise() 將在后面說(shuō)明。

jQuery 的 Deferred/Promise

jQuery 中最常用的 Promise 對(duì)象是 $.ajax() 返回的,最常用的方法不是 then,而是 done、failalways。除了 $.ajax() 外,jQuery 也提供了 $.get()、$.post()$.getJSON() 等簡(jiǎn)化 Ajax 調(diào)用,它們返回的和 $.ajax() 的返回值一樣,是個(gè) Promise 對(duì)象。

實(shí)際上 $.ajax() 返回的是一個(gè) jqXHR 對(duì)象。但 jqXHR 實(shí)現(xiàn)了 jQuery 的 Promise 接口,所以也是一個(gè) Promise 對(duì)象。

done()、fail()always()

done() 添加 deferred.resolve() 的回調(diào),fail() 添加 deferred.reject() 的回調(diào)。所以在 Ajax 調(diào)用成功的情況下執(zhí)行 done() 添加的回調(diào),調(diào)用失敗時(shí)執(zhí)行 fail() 添加的回調(diào)。但不管成功與否,都會(huì)執(zhí)行 always() 添加的回調(diào)。

這里 done()、fail()always() 都是以類似事件的方式添加回調(diào),也就意味著,不管執(zhí)行多次次 done()fail()always(),它們添加的若干回調(diào)都會(huì)在符合的條件下依次執(zhí)行。

一般情況下會(huì)這樣執(zhí)行 Ajax

// 禁用按鈕以避免重復(fù)提交
$("#theButton").prop({
    disabled: true
});

// 調(diào)用 Ajax 提交數(shù)據(jù),假設(shè)返回的是 JSON 數(shù)據(jù)
var jqxhr = $.ajax("do/example", {
    type: "post",
    dataType: "json",
    data: getFormData()
});

jqxhr.done(function(jsonObject) {
    // Ajax 調(diào)用成功
    console.log("success with data", jsonObject);
}).fail(function() {
    // Ajax 調(diào)用失敗
    console.log("failed")
}).always(function() {
    // 不管成功與否,都會(huì)執(zhí)行,取消按鈕的禁用狀態(tài)
    $("#theButton").prop({
        disabled: false
    });
});

上面是最普通最常用的用法,但是在一個(gè)項(xiàng)目中總是這么寫 Ajax,有點(diǎn)累,稍微約定一下再封裝一下就使用起來(lái)就會(huì)便捷得多。首先,假設(shè)我們定義返回的 JSON 是這樣的格式:

{
    "code": "int, 0 表示成功,其它值表示出錯(cuò)",
    "message": "string, 附加的消息,可選",
    "data": "object,附加的數(shù)據(jù),可選
}

然后為項(xiàng)目公共類 app 定義一個(gè) ajax 方法

app.ajax = function(button, url, data) {
    if (button) {
        button.prop("disabled", true);
    }

    return $.ajax(url, {
        type: "post",
        dataType: "json",
        data: data
    }).done(function(json) [
        if (json.code !== 0) {
            showError(json.message || "操作發(fā)生錯(cuò)誤");
        }
    }).fail(function() {
        showError("服務(wù)器錯(cuò)誤,請(qǐng)稍后再試");
    }).always(function() {
        if (button) {
            button.prop("disabled", false);
        }
    });
};

// 調(diào)用
app.ajax("do/example", getFormData()).done(function(json) {
    if (json.code === 0) {
        // 只需要處理正確的情況啦
    }
});

不過(guò)還是有點(diǎn)不爽,如果不需要判斷 json.code === 0 就更好了。這個(gè)……可以自己用一個(gè) Deferred 來(lái)處理:

app.ajax = function(button, url, data) {
    if (button) {
        button.prop("disabled", true);
    }

    var deferred = $.Deferred();
    $.ajax(url, {
        type: "post",
        dataType: "json",
        data: data
    }).done(function(json) [
        if (json.code !== 0) {
            showError(json.message || "操作發(fā)生錯(cuò)誤");
            deferred.reject();
        } else {
            deferred.resolve(json);
        }
    }).fail(function() {
        showError("服務(wù)器錯(cuò)誤,請(qǐng)稍后再試");
        deferred.reject();
    }).always(function() {
        if (button) {
            button.prop("disabled", false);
        }
    });
    return deferred.promise();
};

// 調(diào)用
app.ajax("do/example", getFormData()).done(function(json) {
    // json.code === 0 總是成立
    // 正常處理 json.data 就好
});

注意,這里已經(jīng)不是直接返回 $.ajax() 的結(jié)果 jqXHR 對(duì)象了,返回的是新建 Deferred 對(duì)象的 promise 對(duì)象。

復(fù)習(xí)了 Ajax,現(xiàn)在需要切入正題,找到 jQuery Promise 和 ES6 Promise 接近的地方——then()。

jQuery deferred.then()

在 jQuery 1.8 以前(不含 1.8,比如 jQuery 1.7.2),deferred.then() 就是一個(gè)把 done()fail() 放在一起的語(yǔ)法糖。jQuery 在 1.8 版本的時(shí)候修改了 deferred.then() 的行為,使 then() 的行為與 Promise 的 then() 相似。從 jQuery 的文檔可以看到 1.8 版本的變化——干掉了 callback,換成了 filter:

// version added: 1.5, removed: 1.8
deferred.then( doneCallbacks, failCallbacks )

// version added: 1.7, removed: 1.8
deferred.then( doneCallbacks, failCallbacks [, progressCallbacks ] )

// version added: 1.8
deferred.then( doneFilter [, failFilter ] [, progressFilter ] )

可以簡(jiǎn)單的把 callback 當(dāng)作一個(gè)事件處理,值用于 callback 之后一般不會(huì)改變;而 filter 不同,一個(gè)值傳入 filter 再?gòu)?filter 返回出來(lái),可能已經(jīng)變了。還是舉個(gè)例子來(lái)說(shuō)明

var deferred = $.Deferred();
var promise = deferred.promise();
promise.then(function(v) {
    console.log(`then with ${v}`);
}).done(function(v) {
    console.log(`done with ${v}`);
});
deferred.resolve("resolveData");

在 jQuery 1.7.2 中的結(jié)果

then with resolveData
done with resolveData

在 jQuery 1.8.0 中的結(jié)果

then with resolveData
done with undefined

從上面來(lái)看,jQuery 的 deferred.then() 語(yǔ)義和 ES6 Promise.then() 語(yǔ)義基本一致。如果把上面的 app.ajax 換成 then() 實(shí)現(xiàn)會(huì)有助于對(duì) ES6 Promise 的理解。

app.ajax = function(button, url, data) {
    if (button) {
        button.prop("disabled", true);
    }

    return $.ajax(url, {
        type: "post",
        dataType: "json",
        data: data
    }).then(function(json) {
        if (json.code !== 0) {
            showError(json.message || "操作發(fā)生錯(cuò)誤");
            return $.Deferred().reject().promise();
        } else {
            return $.Deferred().resolve(json).promise();
        }
    }, function() {
        showError("服務(wù)器錯(cuò)誤,請(qǐng)稍后再試");
        deferred.reject();
    }).always(function() {
        if (button) {
            button.prop("disabled", false);
        }
    });
};

// 調(diào)用方式?jīng)]變,用 done,也可以用 then
app.ajax("do/example", getFormData()).done(function(json) {
    // json.code === 0 總是成立
    // 正常處理 json.data 就好
});

從 jQuery Promise 到 ES6 Promise

上面的代碼太長(zhǎng),提煉一下關(guān)鍵部分(示意,不能運(yùn)行)

var promise = $.ajax();
promise.then(function(data) {
    // resolve
    return data.code
        ? new Promise().reject()
        : new Promise().resolve(data);
        
    // 如果沒有錯(cuò),就返回一個(gè)新的 promise,并使用 data 來(lái) resolve,
    // 也可以直接返回 data,
    // 這樣后面 then 的 resolve 部分才能收到數(shù)據(jù)
}, function() {
    // rejected
});

// 調(diào)用階段
promise.then(function(data) {
    // 處理 data
});

也許你沒注意到,其實(shí)上面的代碼基本上就是 ES6 的 Promise 了。下面正式用 ES6 Promise 改寫上面的示意代碼

var promise = new Promise(function(resolve, reject) {
    $.ajax().then(resolve, reject);
    // 上面這句沒看懂?那換成這樣你一定會(huì)懂
    // $.ajax().then(function(data) {
    //     resolve(data);
    // }, function() {
    //     reject();
    // });
}).then(function(data) {
    return data.code
        ? Promise.reject()
        : Promise.resolve(data);

    // 這里 Promise.resolve(data) 同樣可以直接替換為 data
});

// 調(diào)用沒變
promise.then(function(data) {
    // 處理 data
});

怎么樣,差別不大吧。不知不覺就會(huì) ES6 Promise 了!

ES6 的 Promise

上面已經(jīng)把 ES6 的 Promise 帶出來(lái)了,現(xiàn)在只需要把常用方法列出來(lái)作為參考即可

注意,小寫的 promise 表示 Promise 對(duì)象

  • new Promise(executor),產(chǎn)生一個(gè)新的 Promise 對(duì)象

    > `executor(resolve, reject)`
    > `executor`、`resolve` 和 `reject` 均為函數(shù),在 `executor` 中,正確處理調(diào)用 `resolve()` 返回?cái)?shù)據(jù),異常處理直接 `throw new Error(...)` 或調(diào) `reject()` 返回?cái)?shù)據(jù)。
    
  • Promise.resolve(data),產(chǎn)生 Promise 對(duì)象并 resolve

  • Promise.reject(),產(chǎn)生 Promise 對(duì)象并 reject


  • promise.then(onResolve, onReject),然后……繼續(xù)處理

  • promise.catch(onReject),project.then(null, onReject) 的語(yǔ)法糖,和 jQuery 的 promise.fail() 差不多(但不同)。

參考

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)