天天看點

通過 ES6 Promise 和 jQuery Deferred 的異同學習 Promise

Deferred 和 Promise

ES6 和 jQuery 都有 Deffered 和 Promise,但是略有不同。不過它們的作用可以簡單的用兩句話來描述

  • Deffered 觸發 resolve 或 reject
  • Promise 中申明 resolve 或 reject 後應該做什麼(回調)

在 jQuery 中

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

在 ES6 中

var deferred = Promise.defer();
var promise= defered.promise;           
MDN 宣布 Deferred 在 Gecko 30 中被申明為過期,不應該再使用,而應該用

new Promise()

來代替。關于

new Promise()

将在後面說明。

jQuery 的 Deferred/Promise

jQuery 中最常用的 Promise 對象是

$.ajax()

傳回的,最常用的方法不是

then

,而是

done

fail

always

。除了

$.ajax()

外,jQuery 也提供了

$.get()

$.post()

$.getJSON()

等簡化 Ajax 調用,它們傳回的和

$.ajax()

的傳回值一樣,是個 Promise 對象。

實際上

$.ajax()

傳回的是一個 jqXHR 對象。但 jqXHR 實作了 jQuery 的 Promise 接口,是以也是一個 Promise 對象。

done()

fail()

always()

done()

添加

deferred.resolve()

的回調,

fail()

deferred.reject()

的回調。是以在 Ajax 調用成功的情況下執行

done()

添加的回調,調用失敗時執行

fail()

添加的回調。但不管成功與否,都會執行

always()

添加的回調。

這裡

done()

fail()

always()

都是以類似事件的方式添加回調,也就意味着,不管執行多次次

done()

fail()

always()

,它們添加的若幹回調都會在符合的條件下依次執行。

一般情況下會這樣執行 Ajax

// 禁用按鈕以避免重複送出
$("#theButton").prop({
    disabled: true
});

// 調用 Ajax 送出資料,假設傳回的是 JSON 資料
var jqxhr = $.ajax("do/example", {
    type: "post",
    dataType: "json",
    data: getFormData()
});

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

上面是最普通最常用的用法,但是在一個項目中總是這麼寫 Ajax,有點累,稍微約定一下再封裝一下就使用起來就會便捷得多。首先,假設我們定義傳回的 JSON 是這樣的格式:

{
    "code": "int, 0 表示成功,其它值表示出錯",
    "message": "string, 附加的消息,可選",
    "data": "object,附加的資料,可選
}           

然後為項目公共類

app

定義一個

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 || "操作發生錯誤");
        }
    }).fail(function() {
        showError("伺服器錯誤,請稍後再試");
    }).always(function() {
        if (button) {
            button.prop("disabled", false);
        }
    });
};

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

不過還是有點不爽,如果不需要判斷

json.code === 0

就更好了。這個……可以自己用一個 Deferred 來處理:

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 || "操作發生錯誤");
            deferred.reject();
        } else {
            deferred.resolve(json);
        }
    }).fail(function() {
        showError("伺服器錯誤,請稍後再試");
        deferred.reject();
    }).always(function() {
        if (button) {
            button.prop("disabled", false);
        }
    });
    return deferred.promise();
};

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

注意,這裡已經不是直接傳回

$.ajax()

的結果 jqXHR 對象了,傳回的是建立

Deferred

對象的

promise

對象。

複習了 Ajax,現在需要切入正題,找到 jQuery Promise 和 ES6 Promise 接近的地方——

then()

jQuery

deferred.then()

在 jQuery 1.8 以前(不含 1.8,比如 jQuery 1.7.2),

deferred.then()

就是一個把

done()

fail()

放在一起的文法糖。jQuery 在 1.8 版本的時候修改了

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 ] )           

可以簡單的把 callback 當作一個事件處理,值用于 callback 之後一般不會改變;而 filter 不同,一個值傳入 filter 再從 filter 傳回出來,可能已經變了。還是舉個例子來說明

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 中的結果

then with resolveData
done with resolveData           

在 jQuery 1.8.0 中的結果

then with resolveData
done with undefined           

從上面來看,jQuery 的

deferred.then()

語義和 ES6

Promise.then()

語義基本一緻。如果把上面的

app.ajax

換成

then()

實作會有助于對 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 || "操作發生錯誤");
            return $.Deferred().reject().promise();
        } else {
            return $.Deferred().resolve(json).promise();
        }
    }, function() {
        showError("伺服器錯誤,請稍後再試");
        deferred.reject();
    }).always(function() {
        if (button) {
            button.prop("disabled", false);
        }
    });
};

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

從 jQuery Promise 到 ES6 Promise

上面的代碼太長,提煉一下關鍵部分(示意,不能運作)

var promise = $.ajax();
promise.then(function(data) {
    // resolve
    return data.code
        ? new Promise().reject()
        : new Promise().resolve(data);

    // 如果沒有錯,就傳回一個新的 promise,并使用 data 來 resolve,
    // 也可以直接傳回 data,
    // 這樣後面 then 的 resolve 部分才能收到資料
}, function() {
    // rejected
});

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

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

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

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

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

怎麼樣,差别不大吧。不知不覺就會 ES6 Promise 了!

ES6 的 Promise

上面已經把 ES6 的 Promise 帶出來了,現在隻需要把常用方法列出來作為參考即可

注意,小寫的

promise

表示

Promise

對象
  • new Promise(executor)

    ,産生一個新的 Promise 對象
  • Promise.reject()

    ,産生 Promise 對象并

    reject

  • promise.catch(onReject)

    project.then(null, onReject)

    的文法糖,和 jQuery 的

    promise.fail()

    差不多(但不同)。

參考

  • ECMAScript 2015 Language Specification - ECMA-262 6th Edition
  • Deferred - Mozilla | MDN
  • Promise - Mozilla | MDN
  • Deferred Object | jQuery Documentation

繼續閱讀