阅读(3885) (1)

jQuery的Deferred机制

2017-07-21 18:22:10 更新

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