經過 ES6 Promise 和 jQuery Deferred 的異同窗習 Promise

Deferred 和 Promise

ES6 和 jQuery 都有 Deffered 和 Promise,可是略有不一樣。不過它們的做用能夠簡單的用兩句話來描述javascript

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

在 jQuery 中html

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

在 ES6 中java

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

MDN 宣佈 Deferred 在 Gecko 30 中被申明爲過時,不該該再使用,而應該用 new Promise() 來代替。關於 new Promise() 將在後面說明。jquery

jQuery 的 Deferred/Promise

jQuery 中最經常使用的 Promise 對象是 $.ajax() 返回的,最經常使用的方法不是 then,而是 donefailalways。除了 $.ajax() 外,jQuery 也提供了 $.get()$.post()$.getJSON() 等簡化 Ajax 調用,它們返回的和 $.ajax() 的返回值同樣,是個 Promise 對象。ajax

實際上 $.ajax() 返回的是一個 jqXHR 對象。但 jqXHR 實現了 jQuery 的 Promise 接口,因此也是一個 Promise 對象。json

done()fail()always()

done() 添加 deferred.resolve() 的回調,fail() 添加 deferred.reject() 的回調。因此在 Ajax 調用成功的狀況下執行 done() 添加的回調,調用失敗時執行 fail() 添加的回調。但無論成功與否,都會執行 always() 添加的回調。api

這裏 done()fail()always() 都是以相似事件的方式添加回調,也就意味着,無論執行屢次次 done()fail()always(),它們添加的若干回調都會在符合的條件下依次執行。promise

通常狀況下會這樣執行 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 是這樣的格式:markdown

{
    "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 對象

    executor(resolve, reject)
    executorresolvereject 均爲函數,在 executor 中,正確處理調用 resolve() 返回數據,異常處理直接 throw new Error(...) 或調 reject() 返回數據。

  • Promise.resolve(data),產生 Promise 對象並 resolve

  • Promise.reject(),產生 Promise 對象並 reject

  • promise.then(onResolve, onReject),而後……繼續處理

  • promise.catch(onReject)project.then(null, onReject) 的語法糖,和 jQuery 的 promise.fail() 差很少(但不一樣)。

參考

相關文章
相關標籤/搜索