6-4 Ajax的deferred实现
本节编程练习不计算学习进度,请电脑登录imooc.com操作

Ajax的deferred实现

在异步机制这章我们详细的分析了 deferred 的设计,其中提供了 deferred.promise 方法就是把普通对象转化成 deferred 对象了ajax 就是把 deferred 对象给掺进去可以让整个 Ajax 方法变成了一个 deferred 对象,在Ajax方法中返回的是 jqXHR 一个包装对象,在这个对象里面混入了所有实现方法。

ajax: function(url, options) {
    var jqXHR = {} //ajax对象
    deferred = jQuery.Deferred()
    //转成deferred对象
    deferred.promise(jqXHR).complete = completeDeferred.add
    return jqXHR
}

jQuery.ajax 的版本迭代:

为了向后兼容 XMLHttpRequest ,jqXHR 对象将公开下列属性和方法:

readyState
status
statusText
responseXML and/or responseText 当底层的请求分别作出XML和/或文本响应
setRequestHeader(name, value) 从标准出发,通过替换旧的值为新的值,而不是替换的新值到旧值
getAllResponseHeaders()
getResponseHeader()
abort()


为了实现以上这些功能,jQuery 在对 jqXHR 做2个处理:

  1. 异步队列 deferred
  2. 回调队列 Callbacks
// Deferreds
deferred = jQuery.Deferred(),
//所有的回调队列,不管任何时候增加的回调保证只触发一次
completeDeferred = jQuery.Callbacks("once memory"),

给 jqXHR 扩充添加 promise 的属性和方法,然后添加 complete 方法,这里用的是回调列表的 add 方法(即添加回调)

deferred.promise(jqXHR).complete = completeDeferred.add;

此时的 jqXHR 就具有了 promise 的一些特性了与 callback 的回调列队了,当然这里有个重点,返回了一个只读的 deferred 对象,如果返回完整的 deferred 对象,那么外部程序就能随意的触发 deferred 对象的回调函数,很有可能在 AJAX 请求结束前就触发了回调函数(resolve),这就是与 AJAX 本身的逻辑相违背了。所以为了避免不经意间改变任务的内部流程,我们应该只返回 deferred 的只读版本 deferred.promise(),然后把对应的 done 与 fail 改成别名 success 与 error。

jqXHR.success = jqXHR.done;
jqXHR.error   = jqXHR.fail

 

我们还需要把用户自定的内部回调函数给注册到 jqXHR 对象上。

// 增加回调队列
for (i in {
    success  : 1,
    error    : 1,
    complete : 1
}) {
    /**
     * 把参数的回调函数注册到内部jqXHR对象上,实现统一调用
     * 给ajax对象注册 回调函数add
     * deferred返回complete,error外部捕获
     */
    jqXHR[i](s[i]);
}

通过一个 for 循环把对应的方法都执行了,具体就是这几个:

  1. jqXHR.success(s.success)  -> jqXHR.done -> jQuery.Callbacks("once memory")
  2. jqXHR.error(s.error)  -> jqXHR.fail -> jQuery.Callbacks("once memory")
  3. jqXHR.complete(s.complete) -> jQuery.Callbacks("once memory").add(s.success

 

我们参考右边 Ajax 的模拟实现代码。

 

任务

  1. <!doctype html>
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
  5. <title>模拟ajax的 script请求</title>
  6. <script src="http://code.jquery.com/jquery-latest.js"></script>
  7. </head>
  8. <body>
  9.  
  10. <button id="test">模拟jQuery.ajax的script请求</button>
  11.  
  12. <script type="text/javascript">
  13.  
  14. /**
  15. * 模拟ajax的 script请求
  16. * @param {[type]} options [description]
  17. * @return {[type]} [description]
  18. */
  19. function createAjax(options) {
  20.  
  21. if (typeof url === "object") {
  22. options = url;
  23. url = undefined;
  24. }
  25.  
  26. options = options || {};
  27.  
  28. /**
  29. * 参数
  30. * jQuery.ajaxSetup 是默认参数
  31. * @type {[type]}
  32. */
  33. var s = jQuery.ajaxSetup({}, options);
  34.  
  35. // Deferreds
  36. // 异步机制
  37. var deferred = jQuery.Deferred();
  38. var completeDeferred = jQuery.Callbacks("once memory");
  39.  
  40. /**
  41. * 实际返回的ajax对象
  42. * @type {Object}
  43. */
  44. var jqXHR = {}
  45.  
  46. // 把jqXHR对象转化promise对象,幷加入complete、success、error方法
  47. deferred.promise(jqXHR).complete = completeDeferred.add;
  48. //别名
  49. jqXHR.success = jqXHR.done;
  50. jqXHR.error = jqXHR.fail;
  51.  
  52. // 增加回调队列
  53. // complete: function() {
  54. // console.log('局部事件complete')
  55. // },
  56. // error: function() {
  57. // console.log('局部事件error请求失败时调用此函数')
  58. // },
  59. // success: function() {
  60. // console.log('局部事件success')
  61. // }
  62. for (i in {
  63. success: 1,
  64. error: 1,
  65. complete: 1
  66. }) {
  67. jqXHR[i](s[i]);
  68. }
  69.  
  70. function send(_, complete) {
  71. var script = jQuery("<script>").prop({
  72. async: true,
  73. charset: s.scriptCharset,
  74. src: s.url
  75. }).on(
  76. "load error",
  77. callback = function(evt) {
  78. script.remove();
  79. callback = null;
  80. if (evt) {
  81. complete(evt.type === "error" ? 404 : 200, evt.type);
  82. }
  83. }
  84. );
  85. document.head.appendChild(script[0]);
  86. }
  87.  
  88.  
  89. function done(status, nativeStatusText, responses, headers) {
  90. var isSuccess = status >= 200 && status < 300 || status === 304;
  91. var success = jqXHR.success;
  92. var error = jqXHR.error;
  93. if (isSuccess) {
  94. deferred.resolveWith(document, [success, jqXHR]);
  95. } else {
  96. deferred.rejectWith(document, [jqXHR, error]);
  97. }
  98. }
  99.  
  100. //发送请求
  101. send({
  102. Accept: "text/javascript, application/javascript, application/ecmascri"
  103. }, done);
  104.  
  105. return jqXHR;
  106. }
  107.  
  108. function show(data){
  109. $('body').append('<li>'+ data +'</li>');
  110. }
  111.  
  112. $("#test").click(function(){
  113. //执行一个异步的HTTP(Ajax)的请求。
  114. var ajax = createAjax({
  115. url: 'http://code.jquery.com/jquery-latest.js',
  116. dataType: 'script',
  117. //请求完成后回调函数 (请求success 和 error之后均调用)
  118. complete: function() {
  119. show('局部事件complete')
  120. },
  121. error: function() {
  122. show('局部事件error请求失败时调用此函数')
  123. },
  124. success: function() {
  125. show('局部事件success')
  126. }
  127. })
  128.  
  129. ajax.done(function() {
  130. show('deferred done')
  131. }).fail(function() {
  132. show('deferred fail')
  133. }).always(function() {
  134. show('deferred lways')
  135. })
  136.  
  137. })
  138.  
  139.  
  140. </script>
  141. </body>
  142. </html>
下一节