mominger / blog

Tech blog
45 stars 3 forks source link

Zepto源码分析-deferred模块 #14

Open mominger opened 9 years ago

mominger commented 9 years ago

源码注释

//     Zepto.js
//     (c) 2010-2015 Thomas Fuchs
//     Zepto.js may be freely distributed under the MIT license.
//
//     Some code (c) 2005, 2013 jQuery Foundation, Inc. and other contributors

;(function($){
  var slice = Array.prototype.slice

  function Deferred(func) {

    //元组:描述状态、状态切换方法名、对应状态执行方法名、回调列表的关系
      //tuple引自C++/python,和list的区别是,它不可改变 ,用来存储常量集
    var tuples = [
          // action, add listener, listener list, final state
          [ "resolve", "done", $.Callbacks({once:1, memory:1}), "resolved" ],
          [ "reject", "fail", $.Callbacks({once:1, memory:1}), "rejected" ],
          [ "notify", "progress", $.Callbacks({memory:1}) ]
        ],
        state = "pending", //Promise初始状态

        //promise对象,promise和deferred的区别是:
        /*promise只包含执行阶段的方法always(),then(),done(),fail(),progress()及辅助方法state()、promise()等。
          deferred则在继承promise的基础上,增加切换状态的方法,resolve()/resolveWith(),reject()/rejectWith(),notify()/notifyWith()*/
        //所以称promise是deferred的只读副本
        promise = {
            /**
             * 返回状态
             * @returns {string}
             */
          state: function() {
            return state
          },
            /**
             * 成功/失败状态的 回调调用
             * @returns {*}
             */
          always: function() {
            deferred.done(arguments).fail(arguments)
            return this
          },
            /**
             *
             * @returns promise对象
             */
          then: function(/* fnDone [, fnFailed [, fnProgress]] */) {
            var fns = arguments

            //注意,这无论如何都会返回一个新的Deferred只读副本,
            //所以正常为一个deferred添加成功,失败,千万不要用then,用done,fail
            return Deferred(function(defer){
              $.each(tuples, function(i, tuple){
                //i==0: done   i==1: fail  i==2 progress
                var fn = $.isFunction(fns[i]) && fns[i]

                //执行新deferred done/fail/progress
                deferred[tuple[1]](function(){
                    //直接执行新添加的回调 fnDone fnFailed fnProgress
                  var returned = fn && fn.apply(this, arguments)

                    //返回结果是promise对象
                  if (returned && $.isFunction(returned.promise)) {
                     //转向fnDone fnFailed fnProgress返回的promise对象
                     //注意,这里是两个promise对象的数据交流
                      //新deferrred对象切换为对应的成功/失败/通知状态,传递的参数为 returned.promise() 给予的参数值
                    returned.promise()
                      .done(defer.resolve)
                      .fail(defer.reject)
                      .progress(defer.notify)
                  } else {
                    var context = this === promise ? defer.promise() : this,
                        values = fn ? [returned] : arguments
                    defer[tuple[0] + "With"](context, values)//新deferrred对象切换为对应的成功/失败/通知状态
                  }
                })
              })
              fns = null
            }).promise()
          },

            /**
             * 返回obj的promise对象
             * @param obj
             * @returns {*}
             */
          promise: function(obj) {
            return obj != null ? $.extend( obj, promise ) : promise
          }
        },

        //内部封装deferred对象
        deferred = {}

    //给deferred添加切换状态方法
    $.each(tuples, function(i, tuple){
      var list = tuple[2],//$.Callback
          stateString = tuple[3]//   状态 如 resolved

        //扩展promise的done、fail、progress为Callback的add方法,使其成为回调列表
        //简单写法:  promise['done'] = jQuery.Callbacks( "once memory" ).add
        //         promise['fail'] = jQuery.Callbacks( "once memory" ).add  promise['progress'] = jQuery.Callbacks( "memory" ).add
      promise[tuple[1]] = list.add

        //切换的状态是resolve成功/reject失败
        //添加首组方法做预处理,修改state的值,使成功或失败互斥,锁定progress回调列表,
      if (stateString) {
        list.add(function(){
          state = stateString

            //i^1  ^异或运算符  0^1=1 1^1=0,成功或失败回调互斥,调用一方,禁用另一方
        }, tuples[i^1][2].disable, tuples[2][2].lock)
      }

        //添加切换状态方法 resolve()/resolveWith(),reject()/rejectWith(),notify()/notifyWith()
      deferred[tuple[0]] = function(){
        deferred[tuple[0] + "With"](this === deferred ? promise : this, arguments)
        return this
      }
      deferred[tuple[0] + "With"] = list.fireWith
    })

    //deferred继承promise的执行方法
    promise.promise(deferred)

    //传递了参数func,执行
    if (func) func.call(deferred, deferred)

    //返回deferred对象
    return deferred
  }

    /**
     *
     * 主要用于多异步队列处理。
       多异步队列都成功,执行成功方法,一个失败,执行失败方法
       也可以传非异步队列对象

     * @param sub
     * @returns {*}
     */
  $.when = function(sub) {
    var resolveValues = slice.call(arguments), //队列数组 ,未传参数是[]
        len = resolveValues.length,//队列个数
        i = 0,
        remain = len !== 1 || (sub && $.isFunction(sub.promise)) ? len : 0, //子def计数
        deferred = remain === 1 ? sub : Deferred(),//主def,如果是1个fn,直接以它为主def,否则建立新的Def
        progressValues, progressContexts, resolveContexts,
        updateFn = function(i, ctx, val){
          return function(value){
            ctx[i] = this    //this
            val[i] = arguments.length > 1 ? slice.call(arguments) : value   // val 调用成功函数列表的参数
            if (val === progressValues) {
              deferred.notifyWith(ctx, val)  // 如果是通知,调用主函数的通知,通知可以调用多次
            } else if (!(--remain)) {          //如果是成功,则需等成功计数为0,即所有子def都成功执行了,remain变为0,
              deferred.resolveWith(ctx, val)      //调用主函数的成功
            }
          }
        }

      //长度大于1,
    if (len > 1) {
      progressValues = new Array(len) //
      progressContexts = new Array(len)
      resolveContexts = new Array(len)

      //遍历每个对象
      for ( ; i < len; ++i ) {
         //如果是def,
        if (resolveValues[i] && $.isFunction(resolveValues[i].promise)) {
          resolveValues[i].promise()
            .done(updateFn(i, resolveContexts, resolveValues)) //每一个成功
            .fail(deferred.reject)//直接挂入主def的失败通知函数,当某个子def失败时,调用主def的切换失败状态方法,执行主def的失败函数列表
            .progress(updateFn(i, progressContexts, progressValues))
        } else {
          --remain   //非def,直接标记成功,减1
        }
      }
    }

    //都为非def,比如无参数,或者所有子队列全为非def,直接通知成功,进入成功函数列表
    if (!remain) deferred.resolveWith(resolveContexts, resolveValues)
    return deferred.promise()
  }

  $.Deferred = Deferred
})(Zepto)

Promises/A+

由于deferred是基于Promise规范,我们首先需要理清楚Promises/A+是什么。 它的规范内容大致如下

  • 一个promise可能有三种状态:等待(pending)、已完成(fulfilled)、已拒绝(rejected)
  • 一个promise的状态只可能从“等待”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转
  • promise必须实现then方法(可以说,then就是promise的核心),而且then必须返回一个promise,同一个promise的then可以调用多次,并且回调的执行顺序跟它们被定义时的顺序一致
  • then方法接受两个参数,第一个参数是成功时的回调,在promise由“等待”态转换到“完成”态时调用,另一个是失败时的回调,在promise由“等待”态转换到“拒绝”态时调用。同时,then可以接受另一个promise传入,也接受一个“类then”的对象或方法,即thenable对象。
    先用伪代码来实现其规范内容
       //普通的异步回调写法。
        function fA(){
            var a1,a2;

            //出现异常,调用其他方法
            try{
                fa1(a1);
                fa2(a2);
            }catch(e){
                efa1(a1);
                efa2(a2);
            }
        }

        function fa2(){
            fB();//调用另一个和fA类似的异步回调
        }

        //下面采用Promise规范来改写

       //初始化: 等待状态  pending
        var Promise = {
            status: pending,  //状态
            promise: function(o){
                return {
                    done:done,
                    fail:fail
            }
            },
            //必须申明的then方法
            then:function(fulfilledFn,rejectedFn){
                this.done(fulfilledFn);
                this.fail(rejectedFn);

                //返回promise对象
                return this;
            },

            //当状态切换fulfilled时执行
            done: function(){

            },
            //当状态切换rejected时执行
            fail:function(){

            },

            //切换为已完成状态
            toFulfilled:function(){
                 this.status = 'fulfilled'
            },

            //切换为已拒绝状态
            toRejected:function(){
                this.status = 'rejected'
            }

        }

        //将函数包装成Promise对象,并注册完成、拒绝链方法
        //通过then
        Promise.promise(fA).then(fa1,efa1).then(fa2,efa2);
        //假定fb里还调用了另一个异步FB,
        //之前fA的异步回调执行到fb方法
        var PA = Promise.promise(fA).then(fa,efa).then(fb,efb);
        //再挂上fB的异步回调
        PA.then(fB).then(fb1,efb1).then(fb2,efb2);
Promise规范生命周期

promise 2

Deferred API

deferred 2

Deferred生命周期

deferred 2

Deferred设计

deferred 2