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/PromisejQuery 中最常用的 Promise 对象是 $.ajax() 返回的,最常用的方法不是 then,而是 done、fail 和 always。除了 $.ajax() 外,jQuery 也提供了 $.get()、$.post() 和 $.getJSON() 等简化 Ajax 调用,它们返回的和 $.ajax() 的返回值一样,是个 Promise 对象。
实际上 $.ajax() 返回的是一个 。但 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