5-7 Deferred源码剖析(下)
本节编程练习不计算学习进度,请电脑登录imooc.com操作

Deferred源码剖析(下)

在上一节中构建了deferred对象,实现了done/fail/process和resolve/reject/notify等方法,但是最重要的then,pipe管道接口我们还没有实现,我们考虑下:

var dfd = $.Deferred()
dfd.then(function(preVale) {
  return 2 * preVale   //4
}).then(function(preVale) {
  return 3 * preVale   //12
})
dfd.resolve(2)

then就是pipe,我们可以想象是一个管道,可以对回调模式使用瀑布模型。如案例所示,下一个回调都能取到上一个回调的值,这样一直可以叠加往后传递。

不难看出管道的风格就是链式的操作,每一个链上的结果都会反馈后下一个链,那么这个链式是不是传统的返回自身这个对象this呢?

常规的办法通过数组处理:右侧代码所示。

function aDeferred(){
   //代码右侧代码
}

这样的结构当然是很简陋的,这里我们最终有一个本质的问题没有解决,jQuery中的then的返回还有可能是另一个新的异步模型对象,如ajax,因此还能实现done,fail,always,then等方法。所以采用简陋的数组的方式保存状态是很肤浅的了。

这时候jQuery采取了对象保存处理:

我们可以把每一次的then操作,当做是创建一个新的deferred对象,那么每一个对象都够保存自己的状态与各自的处理方法。通过一个办法把所有的对象操作都串联起来,这就是then或者pipe管道设计的核心思路了。

看jQuery的then结构:

then: function( /* fnDone, fnFail, fnProgress */ ) {
         var fns = arguments;
         return jQuery.Deferred(function(newDefer) {
                   jQuery.each(tuples, function(i, tuple) {
                            deferred[tuple[1]](function() {
                         // deferred[ done | fail | progress ]                   
                    });
               });
         }).promise()

其实在内部创建了一个新的Deferred对象,不过这里的不同是通过传递一个回调函数,参数是newDefer,其实Deferred内部就是为了改变下上下文this为deferred,然后传递deferred给这个回调函数了,所以newDefer就指向内部的deferred对象了。

那么对象之间如何关联?

jQuery.each(tuples, function(i, tuple) {
  //取出参数
  var fn = jQuery.isFunction(fns[i]) && fns[i];
  // deferred[ done | fail | progress ] for forwarding actions to newDefer
  // 添加done fail progress的处理方法
  // 针对延时对象直接做了处理
  deferred[tuple[1]](function() {
    var returned = fn && fn.apply(this, arguments);
    if (returned && jQuery.isFunction(returned.promise)) {
      returned.promise()
        .done(newDefer.resolve)
        .fail(newDefer.reject)
        .progress(newDefer.notify);
    } else {
      newDefer[tuple[0] + "With"](this === promise ? newDefer.promise() : this, fn ? [returned] : arguments);
    }
});

把then的方法通过:

deferred.done
deferred.fail
deferred.progress

加入到上一个对象的各自的执行队列中保存了。这样就实现了不同对象之间的关联调用。

同样如果then返回的是一个promise对象(ajax)的时候:

if (returned && jQuery.isFunction(returned.promise)) {
  returned.promise()
    .done(newDefer.resolve)
    .fail(newDefer.reject)
    .progress(newDefer.notify);

也可以直接处理了。

任务

  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  5. <script src="http://code.jquery.com/jquery-latest.js"></script>
  6. <title></title>
  7. </head>
  8. <body>
  9.  
  10.  
  11. <button>模拟的代码测试</button>
  12.  
  13. <script type="text/javascript">
  14.  
  15. //使用$.Deferred
  16. var dfd = $.Deferred()
  17. dfd.then(function(preVale) {
  18. return 2 * preVale;
  19. }).then(function(preVale) {
  20. return 3 * preVale
  21. }).then(function(preVale) {
  22. $('body').append('<li>使用$.Deferred代码结果:'+ preVale +'</li>')
  23. })
  24.  
  25. dfd.resolve(2)
  26.  
  27.  
  28. //简单模拟
  29. function aDeferred() {
  30. var arr = [];
  31. return {
  32. then: function(fn) {
  33. arr.push(fn)
  34. return this;
  35. },
  36. resolve: function(args) {
  37. var returned;
  38. arr.forEach(function(fn, i) {
  39. var o = returned || args;
  40. returned = fn(o)
  41. })
  42. }
  43. }
  44. }
  45.  
  46.  
  47. $("button").on("click", function() {
  48. var d = aDeferred();
  49. d.then(function(preVale) {
  50. return 2 * preVale //4
  51. }).then(function(preVale) {
  52. return 3 * preVale //4
  53. }).then(function(preVale) {
  54. $('body').append('<li>模拟代码结果:'+ preVale +'</li>')
  55. });
  56. d.resolve(2)
  57. });
  58.  
  59.  
  60.  
  61.  
  62.  
  63. </script>
  64.  
  65. </body>
  66. </html>
下一节