jQuery1.7 Deferred对象与ajax请求


Deferred对象是jQuery 1.7新引入的东西, 非常好用.

Deferred是延迟的意思, 确切的说是延迟队列.

如果觉得困惑, 我们可以先看看$.ajax, ajax里面典型的success和error两个函数就是一种需要延迟执行的场合, 如果ajax请求成功,则执行success, 否则执行error.

Deferred对象将这种任务规范化了, 所以实际上1.7中的ajax的实现, 内部也是使用了Deferred对象.

再来对比一下Deferred对象和ajax请求处理的相通之处. Deferred里面几个主要概念是: done, fail, resolve, reject.

其中done就相当于success, 只不过success只能执行一个函数,而done可以添加多个函数, 保存在队列中. fail则相当于error.

ajax里面当请求成功的时候,执行success,那么Deferred中done队列中的函数什么时候执行? 这取决于Deferred对象的状态, 改变状态的函数包括resolve和reject. resolve表示成功, 状态改变之后, 在状态改变之前加入队列的函数会被执行, 之后加入的函数则会立即执行. reject则是对应失败的情况, 队列是fail队列, 原理和resolve一样.

对于ajax,可以将success函数加入到done队列, 当收到请求成功的消息, 就执行resolve, 否则执行reject. 那么相应的正确的函数就会被执行. 所以Deferred对象完全可以取代原来的ajax的内部机制.

在Javascript中需要延迟执行的任务有很多, 不仅是ajax, 例如jQuery中典型的ready函数就是如此, 他需要等待一个事件的发生, 然后执行预先指定的任务. 还有动态加载脚本, 必须等到脚本加载就绪了再去调用其中的函数. 这些都可以统一到Deferred对象上来.

另外还要一提的是promise. promise是许诺的意思, 作用就是只能用来接收承诺, 而不能用来兑现. 还是以ajax为例子,ajax请求是否成功只有函数内部知道, 外部用户不可能决定请求是否成功, 也就是说不允许在外部调用resolve或reject来改变状态. 这就是promise的作用, 他会返回一个受限制的Deferred对象, 你只能使用他的done或者fail方法, 而不能使用resolve和reject.

jQuery1.7中ajax返回的其实就是promise, 你可以向里面添加done函数或者fail函数, 而最终是调用resolve还是reject则由ajax内部决定.

总结一下, 以前的ajax是这样运作的: 你提供success函数和error函数, ajax负责提交请求和监测请求完成情况, 如果检测到请求成功完成就调用你提供的success, 否则调用error函数. 引进了Deferred之后变成了: 你提供的success函数被加入到ajax内部的Deferred对象的done队列中, error函数则添加到fail队列中, 两者均可以添加多个函数. 当ajax发现请求成功的时候, 调用resolve, 否则调用reject, 从而决定执行哪个队列中的函数.

实例: StackOverflow

StackOverflow中使用了Deffered来动态的加载js文件, 比如根据用户是否登录来加载不同的js文件.

看看其中ready的实现

StackExchange.initialized $.Deferred();
StackExchange.ready function(g) {
    StackExchange.initialized.done(g)
};
if (window.serq)
    for (var 0window.serq.lengthi++)
        StackExchange.ready(window.serq[i]);

这里面initialized是一个Deffered对象, ready所做的就是用done往里面添加函数, 这些函数都会在initialized的状态改变之后执行. 也就是resolve被调用之后.

            StackExchange.using(StackExchange.options.user.isAnonymous "anonymous" "loggedIn"
                                function() {
                                  //加载anonymous成功,则释放initialized队列中的函数
                                  StackExchange.initialized.resolve()
                                }, 
                                !0
                                f.fullPostfix);

上面的函数是在jquery的ready中执行的, using的作用是加载js文件, 如果成功, 则第二个参数中的函数会被执行. using的实现比较复杂, 不过也是使用Deffererd对象完成的. 代码在名为stub.js的文件中. 有兴趣的可以看看.