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 对象
executor(resolve, reject)
executor
、resolve
和reject
均为函数,在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()
差不多(但不同)。