13.3 jqXHR 对象

在发出Ajax请求时,jQuery会帮我们确定取得数据的最佳方式。可用的方式包括标准的XMLHttpRequest对象、微软的ActiveX对象XMLHTTP,或者<script>标签。

由于不同请求使用的数据传输方式可能不一样,那我们就需要一个公共的接口与这些通信交互。为此,jqXHR对象提供了这种接口:在XMLHttpRequest对象可用的情况下,封装该对象的行为;在XMLHttpRequest对象不可用的情况下,则尽可能模拟它。这个对象提供给我们的属性和方法包括:

  • 包含返回数据的.responseText.responseXML

  • 包含状态码和状态描述的.status.statusText

  • 操作与请求一起发送的HTTP头部的.setRequestHeader()

  • 提早中断通信的.abort()

jQuery的所有Ajax方法都会返回jqXHR对象,只要把这个对象保存起来,随后就可以方便地使用这些属性和方法。

13.3.1 Ajax承诺

与标准的XMLHttpRequest对象相比,jqXHR对象有一点非常值得重视,那就是它也是一个承诺对象。在第11章讨论延迟对象时,我们知道可以通过它来设置在某个操作完成后触发的回调函数。Ajax调用就是这样一种操作,而jqXHR对象提供了延迟对象所承诺的方法。

使用这些承诺对象的方法,可以重写$.ajax()调用,把successerror回调函数替换成如下所示,参见代码清单13-7。

代码清单13-7

  1. $.ajax({
  2. url: 'http://book.learningjquery.com/api/',
  3. dataType: 'jsonp',
  4. data: {
  5. title: $('#title').val()
  6. },
  7. timeout: 15000
  8. })
  9. .done(response)
  10. .fail(function() {
  11. $response.html(failed);
  12. });

乍一看,调用.done().fail()与之前的写法相比并没有明显的好处。可是,这两个承诺方法的确是有好处的。第一,可以多次调用这两个方法,根据需要添加多个处理程序。第二,如果把调用$.ajax()的结果保存在一个变量中,那么就可以考虑代码的可读性,在后面再添加处理程序。第三,如果在添加处理程序的时候Ajax操作已经完成,就会立即调用该处理程序。第四,我们最好采用与jQuery库中其他代码一致的语法,这带来的好处不言而喻。

使用承诺方法的另一个好处是可以在请求期间添加一个加载指示器,然后在请求完成时或在其他情况下隐藏它。这时候,使用.always()方法就非常方便,参见代码清单13-8。

代码清单13-8

  1. $ajaxForm.on('submit', function(event) {
  2. event.preventDefault();
  3.  
  4. $response.addClass('loading').empty();
  5.  
  6. $.ajax({
  7. url: 'http://book.learningjquery.com/api/',
  8. dataType: 'jsonp',
  9. data: {
  10. title: $('#title').val()
  11. },
  12. timeout: 15000
  13. })
  14. .done(response)
  15. .fail(function() {
  16. $response.html(failed);
  17. })
  18. .always(function() {
  19. $response.removeClass('loading');
  20. });
  21. });

在发送$.ajax()调用之前,给#response容器添加loading类。而在加载完成时,则删除这个类。这样,就可以进一步增强用户的体验。

接下来,我们把$.ajax()调用的结果保存在变量里,以备将来使用。从中你可以真正理解这种承诺行为带给我们的好处。

13.3.2 缓存响应

如果想重复使用同一段数据,那么重复发送Ajax请求显示是一种浪费。为了避免这样做,可以把返回的数据缓存在一个变量中。在需要使用某些数据时,可以检查缓存中是否有这些数据。如果有,就直接拿来用即可。如果没有,则需要发送Ajax请求,并在它的.done()处理程序中将数据保存在缓存里,然后再操作返回的数据。

说起来简单,做起来得需要好几步。不过,要是能够利用承诺对象的属性,那就非常简单了,如代码清单13-9所示。

代码清单13-9

  1. var api = {};
  2.  
  3. $ajaxForm.on('submit', function(event) {
  4. event.preventDefault();
  5.  
  6. $response.empty();
  7.  
  8. var search = $('#title').val();
  9. if (search == '') {
  10. return;
  11. }
  12.  
  13. $response.addClass('loading');
  14.  
  15. if (!api[search]) {
  16. api[search] = $.ajax({
  17. url: 'http://book.learningjquery.com/api/',
  18. dataType: 'jsonp',
  19. data: {
  20. title: search
  21. },
  22. timeout: 15000
  23. });
  24. }
  25. api[search].done(response).fail(function() {
  26. $response.html(failed);
  27. }).always(function() {
  28. $response.removeClass('loading');
  29. });
  30. });

这里,我们新声明了一个变量,名叫api,用来保存创建的jqXHR对象。这个变量本身是一个对象,它的键对应着执行的搜索关键词。在提交表单时,我们要检查一下jqXHR对象中是否有那个键。如果没有,就像以前一样执行查询,并把结果对象保存在api变量中。

有了jqXHR对象,也就有了.done().fail().always()处理程序。注意,无论是否发送Ajax请求,jqXHR对象都会存在。现在这里需要考虑两种可能性。

首先,如果以前没有查询过,那么可能会发送Ajax请求。这样的结果跟以前一样:发送请求,然后使用承诺方法给jqXHR对象添加处理程序。当服务器返回响应时,触发适当的回调函数,将结果输出到屏幕上。

另一方面,如果以前执行过这个查询,那么jqXHR对象已经保存在api里面了。这一次不会执行新的查询,但我们仍然可以在保存的对象上调用承诺方法。而这会给该对象添加新的处理程序,由于作为延迟对象它已经被解决了,所以会立即触发相关的处理程序。

jQuery的延迟对象系统会在后台把所以这些工作都替我们做了。我们只要写几行代码,就可以消除应用中不必要的网络请求。