JavaScript入门教程

JavaScript简介
JavaScript语法基础
JavaScript流程控制
JavaScript函数
面向对象编程
JavaScript事件
JavaScript DOM
正则表达式
JavaScript BOM
AJAX

专题分析

浏览器兼容性
JS优化
Web前端开发规范
编辑器推荐
总结和笔记

学习助手

对象参考手册
ECMAScript分析
数据中心
QQ交流群

AJAX Promise对象和Deferred对象

2010 年,我和我那能者多劳的Ajax 同事有过这样一次对话。

我:嗨,您能帮我从这个URL 抓取一些数据回来吗?Ajax 大拿:我这就去搞定它!不过你要给我一个success 回调,好让我搞定后就通知你。

我:好,给您。十分感谢。

Ajax 大拿:哦,对了,你还要给我一个error 回调。你知道啦,以防万一嘛!

我:说得不错。还要其他东西吗?

Ajax 大拿:嘿,我发现你给的那两个回调之间有一些代码重复呀!你可以把那些重复的代码移到第三个回调里面,就叫做always回调好了。

我:(不耐烦)好吧好吧,我去把它们重构一下。另外,给您提点意见呀,您干嘛不先去干活儿呢?我稍后再给您那些回调不行吗?

Ajax 大拿:(大怒)那我变成什么了?EventEmitter 对象吗?

幸好,jQuery 1.5 改变了Ajax 大拿那种“马上就要”的态度。我们知道和喜欢的所有Ajax 函数($.ajax、$.get 及$.post)现在都会返回Promise(承诺)对象。Promise 对象代表一项有两种可能结果(成功或失败)的任务,它还持有多个回调,出现不同结果时会分别触发相应的回调。举个例子,jQuery 1.4 中的代码必须写成这样:
$.get('/mydata', {
    success: onSuccess,
    failure: onFailure,
    always: onAlways
});
而到了jQuery 1.5+,可以写成这样:
var promise = $.get('/mydata');
promise.done(onSuccess);
promise.fail(onFailure);
promise.always(onAlways);
大家可能会奇怪:这种变化能有什么好处呢?为什么非得在触发Ajax调用之后再附加回调呢?一言以蔽之:封装。如果Ajax 调用要实现很多效果(既要触发动画,又要插入HTML,还要锁定/解锁用户输入,等等),那么仅由负责发出请求的那部分应用代码来处理所有这些效果,显然很蠢很拙劣。

只传递Promise 对象就会优雅得多。传递Promise 对象就相当于声明:“你感兴趣的某某事就要发生了。想知道什么时候完事吗?给这个Promise 对象一个回调就行啦!”Promise 对象也和EventEmitter 对象一样,允许向同一个事件绑定任意多的处理器(堆积技术)。对于多个Ajax 调用分享某个功能小片段(譬如“正加载”动画)的情况,堆积技术也会使降低代码重复度容易很多。

不过使用Promise 对象的最大优势仍然在于,它可以轻松从现有Promise 对象派生出新的Promise 对象。我们可以要求代表着并行任务的两个Promise 对象合并成一个Promise 对象,由后者负责通知前面那些任务都已完成。也可以要求代表着任务系列中首任务的Promise 对象派生出一个能代表任务系列中末任务的Promise 对象,这样后者就能知道这一系列任务是否均已完成。待会儿我们就会看到,Promise 对象天生就适合用来进行这些操作。