JavaScript入门教程

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

专题分析

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

学习助手

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

AJAX 生成Promise对象

本章一开始就展示了如何用jQuery 1.5+中的Ajax 方法($.ajax、$.get 及$.post)返回Promise 对象。但要想真正理解Promise,我们需要自己动手生成它。

假设我们提示用户应敲击Y 键或N 键。为此要做的第一件事就是生成一个$.Deferred 实例以代表用户做出的决定。
var promptDeferred = new $.Deferred();
promptDeferred.always(function(){ console.log('A choice was made:'); });
promptDeferred.done(function(){ console.log('Starting game...'); });
promptDeferred.fail(function(){ console.log('No game today.'); });
注:always 关键字仅适用于jQuery 1.6+。

大家可能会奇怪:为什么本节叫做“生成Promise 对象”,却要生成一个Deferred(延迟)实例?别担心,Deferred 就是Promise!更准确地说,Deferred 是Promise 的超集,它比Promise 多了一项关键特性:可以直接触发。纯Promise 实例只允许添加多个调用,而且必须由其他什么东西来触发这些调用。

使用resolve(执行)方法和reject(拒绝)方法均可触发Deferred对象。
$('#playGame').focus().on('keypress', function(e) {
    var Y = 121, N = 110;
    if (e.keyCode === Y) {
        promptDeferred.resolve();
    } else if (e.keyCode === N) {
        promptDeferred.reject();
    } else {
        return false; // 这里的Deferred 对象保持着挂起状态
    };
});
请访问http://jsfiddle.net/TrevorBurnham/PJ6Bf/查看这个例子的运行情况。加载页面,敲击Y 键。控制台会这样说:
A choice was made:
Starting game...

大家看懂是怎么回事了吗?执行了Deferred(即对Deferred 对象调用了resolve 方法)之后,即运行该对象的always(恒常)回调和done(已完成)回调。(会按照绑定回调的次序来运行回调,这可不是巧合
哦!)刷新页面,敲击N 键。
A choice was made:
No game today.

这样,拒绝了Deferred(即对Deferred 对象调用了reject 方法)之后,即运行该对象的always 回调和fail(失败)回调。注意,始终会按照绑定回调的次序来运行回调。如果最后绑定的是always 回调,则控制台的输出行顺序会反过来。

再试着反复敲击Y 键和N 键。第一次做出选择之后,就再也没有反应了!这是因为Promise 只能执行或拒绝一次,之后就失效了。我们断言,Promise 对象会一直保持挂起状态,直到被执行或拒绝。对Promise 对象调用state(状态)方法,可以查看其状态是"pending"、"resolved",还是"rejected"。(到jQuery 1.7 才添加了state 方法,此前的版本使用的是isResolved 和isRejected。)

如果正在进行的一次性异步操作的结果可以笼统地分成两种(如成功/失败,或接受/拒绝),则生成Deferred 对象就能直观地表达这次任务。

生成纯Promise对象

我们刚刚了解到Deferred 对象也是Promise 对象,那么,如何得到一个不是Deferred 对象的Promise 对象呢?很简单,对Deferred 对象调用promise 方法即可。

两种说法,一个意思
大家可能已经注意到了,在执行或拒绝Promise的时候,我一直说的是“触发”Promise对象;已执行或已拒绝Promise则称Promise对象“已触发”。这种说法并不标准,不过本章仍会沿用。遗憾的是,jQuery除了拙劣地念叨“非挂起”之外,并没有一种简洁明了的说法来指代那些已执行或已拒绝的Promise对象。

CommonJS的Promises/A规范及其实现中使用了一种更合理的说法:Promise对象已履行(关键字为fulfill)或已拒绝,这两种情况都称Promise已执行。3.7节会对此作进一步阐述。
var promptPromise = promptDeferred.promise();
promptPromise 只是promptDeferred 对象的一个没有resolve/reject 方法的副本。我们把回调绑定至Deferred 或其下辖的Promise 并无不同,因为这两个对象本质上分享着同样的回调。它们也分享着同样的state(返回的状态值为"pending"、"resolved"或"rejected")。这意味着,对同一个Deferred 对象生成多个Promise对象是毫无意义的。事实上,jQuery 给出的只不过是同一个对象。
var promise1 = promptDeferred.promise();
var promise2 = promptDeferred.promise();
console.log(promise1 === promise2); // true
而且,对一个纯Promise 对象再调用promise 方法,产生的只不过是一个指向相同对象的引用。
console.log(promise1 === promise1.promise()); // true
使用promise 方法的唯一理由就是“封装”。如果传递promptPromise对象,但保留promptDeferred 对象为己所用,则可以肯定的是,除非是你自己想触发那些回调,否则任何回调都不会被触发。

在此重申一点:每个Deferred 对象都含有一个Promise 对象,而每个Promise 对象都代表着一个Deferred 对象。有了Deferred 对象,就可以控制其状态,而有了纯Promise 对象,只能读取其状态及附加回调。

jQuery API中的Promise对象

本章开头列举了jQuery 的Ajax 函数($.ajax、$.get 及$.post)可返回的几个Promise 对象。Ajax 是演示Promise 的绝佳用例:每次对远程服务器的调用都或成功或失败,而我们希望以不同的方式来处理这两种情况。不过,Promise 也同样适用于本地的一些异步操作,譬如动画。

在jQuery 中,任何动画方法都可以接受传入的回调,以便在完成动画时发出通知。
$('.error').fadeIn(afterErrorShown);
在jQuery 1.6+中,可以转而要求jQuery 对象生成Promise,后者代表了这个对象已附加动画的完成情况,即是否完成了目前正处于挂起状态的动画。
var errorPromise = $('.error').fadeIn().promise();
errorPromise.done(afterErrorShown);
对同一个jQuery 对象附加的多个动画会排入队列按顺序运行。仅当调用promise 方法之时已入列的全部动画均已执行之后,相应的Promise 对象才会执行。因此,这会产生两个不同的、按顺序执行的Promise 对象(或者根本就不执行,若先调用stop 方法的话)。
var $flash = $('.flash');
var showPromise = $flash.show().promise();
var hidePromise = $flash.hide().promise();
相当简单,对不对?在jQuery 1.6 及jQuery 1.7 中,jQuery 对象的promise 方法只是一种权宜之计。如果使用Deferred 对象的resolve方法作为动画的回调,即可自行轻松生成一个行为完全相同的动画版Promise 对象。
var slideUpDeferred = new $.Deferred();
$('.menu').slideUp(slideUpDeferred.resolve);
var slideUpPromise = slideUpDeferred.promise();
在本书付梓之前刚刚发布的jQuery 1.8 中,动画版Promise 已变成更加强大的对象。动画版Promise 附加了额外的信息,其中包括动画运行过程中的计算值props(这对调试非常有价值)。此外,对动画版Promise 对象,还可以获得进度通知(请参阅3.4 节),以及即时调整动画。有关这些新特性的文档草稿可见于 https://gist.github.com/54829d408993526fe475。

jQuery 1.8 又向jQuery 大家庭中新添了一种Promise 资源:$.ready.promise()也能生成一个Promise 对象,并且当文档就绪时即执行该对象。这意味着以下3 行代码现在是等效的。
$(onReady);
$(document).ready(onReady);
$.ready.promise().done(onReady);
本节介绍了如何获得jQuery 中的Promise 对象:或者生成一个$.Deferred 实例(这会带来一个可自行控制的Promise),或者进行一次可返回Promise 对象的API 调用。以下几节将介绍我们能对这些Promise 对象做些什么。