• javascript 异步编程2


    好像有这么一句名言——"每一个优雅的接口,背后都有一个龌龊的实现"。最明显的例子,jQuery。之所以弄得这么复杂,因为它本来就是那复杂。虽然有些实现相对简明些,那是它们的兼容程度去不了那个地步。当然,世上总有例外,比如mootools,但暴露到我们眼前的接口,又不知到底是那个父类的东西,结构清晰但不明撩。我之所以说这样的话,因为异步列队真的很复杂,但我会尽可能让API简单易用。无new实例化,不区分实例与类方法,链式,等时髦的东西都用上。下面先奉上源码:

    ;(function(){
        var dom = this.dom = this.dom || {
            mix : function(target, source ,override) {
                var i, ride = (override === void 0) || override;
                for (i in source) {
                    if (ride || !(i in target)) {
                        target[i] = source[i];
                    }
                }
                return target;
            }
        }
        //////////////////////////////////////////////////////////////////////
        //=======================异步列队模块===================================
        var Deferred = dom.Deferred = function (fn) {
            return this instanceof Deferred ? this.init(fn) : new Deferred(fn)
        }
        var A_slice = Array.prototype.slice;
        dom.mix(Deferred, {
            get:function(obj){//确保this为Deferred实例
                return  obj instanceof Deferred ? obj : new Deferred
            },
            ok : function (r) {//传递器
                return r
            },
            ng : function (e) {//传递器
                throw  e
            }
        });
        Deferred.prototype = {
            init:function(fn){//初始化,建立两个列队
                this._firing = [];
                this._fired = [];
                if(typeof fn === "function")
                    return this.then(fn)
                return this;
            },
            _add:function(okng,fn){
                var obj = {
                    ok:Deferred.ok,
                    ng:Deferred.ng,
                    arr:[]
                }
                if(typeof fn === "function")
                    obj[okng] = fn;
                this._firing.push(obj);
                return this;
            },
            then:function(fn){//_add的包装方法1,用于添加正向回调
                return  Deferred.get(this)._add("ok",fn)
            },
            once:function(fn){//_add的包装方法2,用于添加负向回调
                return  Deferred.get(this)._add("ng",fn)
            },
            wait:function(timeout){
                var self = Deferred.get(this);
                self._firing.push(~~timeout)
                return self
            },
            _fire:function(okng,args,result){
                var type = "ok",
                obj = this._firing.shift();
                if(obj){
                    this._fired.push(obj);//把执行过的回调函数包,从一个列队倒入另一个列队
                    var self = this;
                    if(typeof obj === "number"){//如果是延时操作
                        var timeoutID = setTimeout(function(){
                            self._fire(okng,self.before(args,result))
                        },obj)
                        this.onabort = function(){
                            clearTimeout(timeoutID );
                        }
                    }else if(obj.arr.length){//如果是并行操作
                        var i = 0, d;
                        while(d = obj.arr[i++]){
                            d.fire(args)
                        }
                    }else{//如果是串行操作
                        try{//
                            result = obj[okng].apply(this,args);
                        }catch(e){
                            type = "ng";
                            result = e;
                        }
                        this._fire(type,this.before(args,result))
                    }
                }else{//队列执行完毕,还原
                    (this.after || Deferred.ok)(result);
                    this._firing = this._fired;
                    this._fired = [];
                }
                return this;
            },
            fire:function(){//执行正向列队
                return this._fire("ok",this.before(arguments));
            },
            error:function(){//执行负向列队
                return this._fire("ng",this.before(arguments));
            },
    
            abort:function(){//中止列队
                (this.onabort || Deferred.ok)();
                return this;
            },
            //每次执行用户回调函数前都执行此函数,返回一个数组
            before:function(args,result){
               return result ? result instanceof Array ? result : [result] : A_slice.call(args)
            },
            //并行操作,并把所有的子线程的结果作为主线程的下一个操作的参数
            paiallel : function (fns) {
                var self = Deferred.get(this),
                obj = {
                    ok:Deferred.ok,
                    ng:Deferred.ng,
                    arr:[]
                },
                count = 0,
                values = {}
                for(var key in fns){//参数可以是一个对象或数组
                    if(fns.hasOwnProperty(key)){
                        (function(key,fn){
                            if (typeof fn == "function")
                                fn = Deferred(fn);
                            fn.then(function(value){
                                values[key] = value;
                                if(--count <= 0){
                                    if(fns instanceof Array){//如果是数组强制转换为数组
                                        values.length = fns.length;
                                        values = A_slice.call(values)
                                    }
                                    self._fire("ok",[values])
                                }
                            }).once(function(e){
                                self._fire("ng",[e])
                            });
                            obj.arr.push(fn);
                            count++
                        })(key,fns[key])
                    }
                }
                self.onabort = function(){
                    var i = 0, d;
                    while(d = obj.arr[i++]){
                        d.abort();
                    }
                }
                self._firing.push(obj);
                return self
            },
            //处理相近的迭代操作
            loop : function (obj, fn, result) {
                obj = {
                    begin : obj.begin || 0,
                    end   : (typeof obj.end == "number") ? obj.end : obj - 1,
                    step  : obj.step  || 1,
                    last  : false,
                    prev  : null
                }
                var step = obj.step,
                _loop = function(i,obj){
                    if (i <= obj.end) {
                        if ((i + step) > obj.end) {
                            obj.last = true;
                            obj.step = obj.end - i + 1;
                        }
                        obj.prev = result;
                        result = fn.call(obj,i);
                        Deferred.get(result).then(_loop).fire(i+step,obj);
                    }else{
                        return result;
                    }
                }
                return (obj.begin <= obj.end) ? Deferred.get(this).then(_loop).fire(obj.begin,obj) : null;
            }
        }
        //将原型方法转换为类方法
        "loop wait then once paiallel".replace(/\w+/g, function(method){
            Deferred[method] = Deferred.prototype[method]
        });
    })();
    
    

    Deferred提供的接口其实不算多,then once loop wait paialle就这五个,我们可以new一个实例出来,用它的实例方法,可以直接用类名加方法名,其实里面还是new了一个实例。另外,还有两个专门用于复写的方法,before与after。before执行于每个回调函数之前,after执行于所有回调之后,相当于complete了。既然是列队,就有入队操作与出队操作,我不可能使用queue与dequeue这样土的命名。queue换成两个时间状语,then与once,相当于jQuery的done、fail,或dojo的addCallback、addErrback。dequeue则用fire与error取替,jQuery1.5的beta版也曾经用过fire,不知为何换成resolve这样难记的单词。好了,我们先不管其他API,现在就试试身手吧。

          var log = function (s) {
            window.console && window.console.log(s);
          }
          dom.Deferred(function () {
            log(1);//1
          })
          .then(function () {
            log(2);//2
          })
          .then(function () {
            log(3);//3
          })
          .fire();
    //如果不使用异步列队,实现这种效果,就需要用套嵌函数
    /*
          var fun = function(fn){
            fn()
          };
          fun(function(){
            log(1);
            fun(function(){
              log(2);
              fun(function(){
                log(3);
              })
            });
          });
    */
    

    当然,现在是同步操作。注意,第一个回调函数是作为构造器的参数传入,可以节约了一个then^_^。

    默认如果回调函数存在返回值,它会把它作为下一个回调函数的参数传入,如:

          dom.Deferred(function (a) {
            a +=  10
            log(a);//11
            return a
          })
          .then(function (b) {
            b += 12
            log(b);//23
            return b
          })
          .then(function (c) {
            c += 130
            log(c);//153
          })
          .fire(1);
    

    我们可以重载before函数,让它的结果不影响下一个回调函数。在多投事件中,我们也可以在before中定义,如果返回false,就中断队列了。

    我们再来看它如何处理异常。dom.Deferred的负向列队与jQuery的是完全不同的,jQuery的只是正向列队的一个克隆,而在dom.Deferred中,负向列队只是用于突发情况,是配角。

          dom.Deferred(function () {
            log(1111111111)//11111111111
          }).
            then(function () {
            throw "error!";//发生错误
          }).
            then(function (e) {//这个回调函数不执行
            log(e+"222222")
            return 2222222
          }).
            once(function(e){//直到 遇上我们自定义的负向回调
            log(e+'333333')//error!333333
            return 333333333
          }).
            then(function (c) {//回到正向列队中
            log(c)//33333333
          }).
            fire()
    

    上面几个例子严格来说是同步执行,想实现异步就要用到setTimeout。当然除了setTimeout,我们还有许多方案,img.onerror script.onreadystatechange script.onload xhr.onreadystatechange self.postMessage……但它们 都有一个缺点,就是不能指定回调函数的执行时间。更何况setTimeout是没有什么兼容问题,如img.onerrot就不能用于IE6-8,postMessage虽然很快,但只支持非常新的浏览器版本。我说过,异步就是延时,延时就是等待,因此这方法叫做wait。

                    dom.Deferred(function(){
                        log(1)
                    }).wait(1000).then(function(){
                        log(2)
                    }).wait(1000).then(function(){
                        log(3)
                    }).wait(1000).then(function(){
                        log(4)
                    }).fire()
    

    好了,我们看异步列队中最难的部分,并行操作。这相当于模拟线程了,两个不相干的东西各自做自己的事,互不干扰。当然在时间我们还是能看出先后顺序来的。担当这职责的方法为paiallel。

          dom.Deferred.paiallel([function(){
              log("司徒正美")
              return 11
            },function(i){
              log("上官莉绮")
              return 12
            },function(i){
              log("朝沐金风")
              return 13
            }]).then(function(d){
            log(d)
          }).fire(10)
    

    不过,上面并没有用到异步,都是同步,这时,paiallel就相当于一个map操作。

            var d = dom.Deferred
            d.paiallel([
              d.wait(2000).then(function(a){
                log("第1个子列队");
                return 123
              }),
              d.wait(1500).then(function(a){
                log("第2个子列队");
                return 456
              }),
              d.then(function(a){
                log("第3个子列队")
                return 789
              })]).then(function(a){
              log(a)
            }).fire(3000);
    

    最后要介绍的是loop方法,它只要改变一下就能当作animate函数使用。

     d.loop(10, function(i){
            log(i);
            return d.wait(500)
      });
    

    添加多个列队,让它们交错进行,模拟“多线程”效果。

          d.loop(10, function(i){
            log("第一个列队的第"+i+"个操作");
            return d.wait(100)
          });
          d.loop(10, function(i){
            log("第二个列队的第"+i+"个操作");
            return d.wait(100)
          });
          d.loop(10, function(i){
            log("第三个列队的第"+i+"个操作");
            return d.wait(100)
          });
    

    当然这些例子都很简单,下次再结合ajax与动画效果说说。

  • 相关阅读:
    git 还原某个文件~~~
    uniqid()
    array_filter 过滤一维中空数组,数组的序列不变
    加密 解密
    gitlab基本维护和使用
    vim 单文件中查找方法
    php数组定义
    $('.goods_tag_ids_all')[0].checked = true;//~~~~~ 单条改变checkbox 属性样式
    由买冰箱想到的
    2014年年终总结
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/1984336.html
Copyright © 2020-2023  润新知