jQuery的Deferred机制
Deferred 对象是在 jQuery1.5 中引入的回调管理对象。其作用,大概就是把一堆函数按顺序放入一个调用链,然后根据状态,来依次调用这些函数。 AJAX 的所有操作都是使用它来进行封装的。比如我们定义的,当请求正常返回时,会调用 success 定义的函数,失败时,会调用 error定义的函数。这里的“失败”,“正常”就是状态,而对应的函数,只是调用链中的一个而且。
先来看一个直观的例子:
var obj = $.Deferred(function(a){}); obj.done(function(){console.log('1')}); obj.done(function(){console.log('2')}); obj.resolve();
这样,我们就可以按顺序看到 1 , 2 这两个输出了。
总的来说, jQuery 的 Deferred 对象有三个状态: done , fail , process 。
- process 只能先于其它两个状态先被激发。
- done 和 fail 互斥,只能激发一个。
- process 可以被重复激发,而 done 和 fail 只能激发一次。
然后, jQuery 提供了一些函数用于添加回调,激发状态等:
- deferred.done()
- 添加一个或多个成功回调。
- deferred.fail()
- 添加一个或多个失败回调。
- deferred.always()
- 添加一个函数,同时应用于成功和失败。
- deferred.progress()
- 添加一个函数用于准备回调。
- deferred.then()
- 依次接受三个函数,分别用于成功,失败,准备状态。
- deferred.reject()
- 激发失败状态。
- deferred.resolve()
- 激发成功状态。
- deferred.notify()
- 激发准备状态。
如果一个 Deferred 已经被激发,则新添加的对应的函数会被立即执行。
除了上面的这些操作函数之外, jQuery 还提供了一个 jQuery.when() 的回调管理函数,可以用于方便地管理多个事件并发的情况,先看一个 AJAX 的“原始状态”例子:
var defer = $.ajax({ url: '/json.html', dataType: 'json' }); defer.done(function(data){console.log(data)});
.done() 做的事和使用 success 定义是一样的。
当我们需要完成,像“请求A和请求B都完成时,执行函数”之类的需求时,使用 $.when() 就可以了:
var defer_1 = $.ajax({ url: '/json.html', dataType: 'json' }); var defer_2 = $.ajax({ url: '/jsonp.html', dataType: 'jsonp' }); var new_defer = $.when(defer_1, defer_2); new_defer.done(function(){console.log('haha')});
在 $.when() 中的 Deferred ,只要有一个是 fail ,则整体结果为 fail 。
Deferred 的回调函数的执行顺序与它们的添加顺序一致。
这里特别注意一点,就是 done / fail / always 与 then 的返回值的区别。从功能上看,它们都可以添加回调函数,但是,方法的返回值是不同的。 前组的返回值是原来的那个 defer 对象,而 then 返回的是一个新的 defer 对象。
then 返回新的 defer 这种形式,可以用于方便地实现异步函数的链式调用。
比如对于:
var defer = $.ajax({ url: '/json', dataType: 'json' });
如果使用 done 方法:
defer.done(function(){ return $.ajax({ url: '/json', dataType: 'json', success: function(){ console.log('inner') } }); }).done(function(){ console.log('here'); });
等同于是调用了两次 defer.done
, defer.done
,注册的两次回调函数依次被执行后,我们看到的输出是:
here inner
这是两次 defer.done
的结果,第一个回调函数返回了一个新的 defer 没任何作用。
如果换成 then 方法的话:
defer.then(function(){ return $.ajax({ url: '/json', dataType: 'json', success: function(){ console.log('inner') } }); }).done(function(){ console.log('here'); });
上面的代码相当于:
var new_defer = defer.then(...); new_defer.done(...);
它跟两次 defer.done
是不同的。 new_defer
会在 inner
那里的 defer 被触发时再被触发,所以输出结果是:
inner here
更一般地来说 then 的行为,就是前面的注册函数的返回值,会作为后面注册函数的参数值:
var defer = $.ajax({ url: '/json', dataType: 'json' }); defer.then(function(res){ console.log(res); return 1; }).then(function(res){ console.log(res); return 2; }).then(function(res){ console.log(res); });
上面代码的输入结果是:
ajax response 1 2