Ruby's Louvre

每天学习一点点算法

导航

解读JSDeferred源码

另一个异步库JS_Randezvous

JSDeferred是日本javascript高手cho45受MochiKit.Async.Deferred模块启发而设计的一个异步执行类库,为javascript实现了java式的next与wait的操作。

入门,创建一个Deferred类

      Deferred.define();
      next(function(){
        /* 处理 */
      })
    

上面会将全局对象变成Deferred的子类,那么它就可以创建Deferred实例。虽然这样很方便,但无疑污染了全局作用域,因此JSDeferred也提供了无侵入的写法:

      var o = {}; //定义一个对象
      Deferred.define(o);//把Deferred的方法加持到它上面,让o成为一个Deferred子类。

      for(var x in o)alert(x);
      // parallel
      // wait
      // next
      // call
      // loop

      o.next(function(){
        /* 处理 */
      })
    

或者直接使用Deferred:

      Deferred.next(function(){//在暗地里创建一个Deferred实例,然后我们直接"链"下去就行了。
        /* 处理 */
      })
    

无论哪一个,最初的next都是一个静态方法,目的是用于提供了一个JSDeferred实例与实现第一个异步操作。异步操作在JSDeferred中有许多实现方法,如setTimeout,img.onerror或 script.onreadystatechange ,它会视浏览器选择最快的异步方式。现在我们看最简单的实现方式,setTimeout。它在next_default静态方法中实现。

      Deferred.next_default = function (fun) {
        var d = new Deferred();
        var id = setTimeout(function () { clearTimeout(id); d.call() }, 0);
        d.canceller = function () { try { clearTimeout(id) } catch (e) {} };
        if (fun) d.callback.ok = fun;
        return d;
      };
    

无论何种next静态方法,最终都会返回一个Deferred实例,因此第二个next方法与第一个next是完全不同。它是一个实例方法,并确定其_post的第一个参数为"ok",并在_post中方法重置callback.ok与设置其_next,从而构造一个如Deferred链

构造Deferred链。

首先,我先提供一个用于讲解的例子吧:

      Deferred.define();
      next(function func1(){
        alert(1)
      })
      .next(function func2(){
        alert(2)
      });
      alert(3)
    

与贴出源码开头的部分:

      function Deferred () { return (this instanceof Deferred) ? this.init() : new Deferred() }
      Deferred.ok = function (x) { return x };
      Deferred.ng = function (x) { throw  x };
      Deferred.prototype = {
        init : function () {
          this._next    = null;
          this.callback = {
            ok: Deferred.ok,
            ng: Deferred.ng
          };
          return this;
        },

        next  : function (fun) { return this._post("ok", fun) },
        error : function (fun) { return this._post("ng", fun) },
        call  : function (val) { return this._fire("ok", val) },
        fail  : function (err) { return this._fire("ng", err) },

        cancel : function () {
          (this.canceller || function () {})();
          return this.init();
        },

        _post : function (okng, fun) {
          this._next =  new Deferred();
          this._next.callback[okng] = fun;
          return this._next;
        },

        _fire : function (okng, value) {
          var next = "ok";
          try {
            value = this.callback[okng].call(this, value);
          } catch (e) {
            next  = "ng";
            value = e;
          }
          if (value instanceof Deferred) {
            value._next = this._next;
          } else {
            if (this._next) this._next._fire(next, value);
          }
          return this;
        }
      };
    

我们整理一下思路:

  • 首先,最初的next会创建一个新的Deferred实例(下简称为d1),然后将func1放入d1.callback.ok中。
  • 接着,第二个的next会创建一个新的Deferred实例(下简称为d2),然后将func2放入d2.callback.ok中。
  • 然后,在_post中将d1._next设置为d2。
  • 最后,setTimeout 0ms会把它里面注册的函数放到当前待执行函数的后面,因此会先执行alert(3),然后再function () { clearTimeout(id); d.call() },再即执行d1.call()。call方法再调用_fire方法,从而执行d1.callback.ok。如果d1.callback.ok的返回值不为Deferred实例,并且d1._next不为空,则执行d1._next.callback.ok,相当于执行d2.callback.ok。

Deferred链的实现。

在上面最后一步我们也提到了,Deferred链是否能延续取决了两个东西,当前Deferred实例在_fire中的执行callback的返回值与其_next。_next的设置目前我们看到有两个地方,_post实例方法与_fire实例方法。当我们执行到第二个next实例方法时,就会调用_post方法。在想调用_fire方法,我们目前只是通过call方法达到,而call位于setTimeout中。由于setTimeout的零秒延迟,换言之,当我们执行call方法时,程序已经为d1准备好callback与_next了,然后再进入_fire方法中:

要注意一下Deferred.prototype._fire内的this实质为d1。

      value = this.callback[okng].call(this, value);
      //相当于d1.callback.ok.call(d1,null)
      //相当于func1.call(d1,null)
      //相当于d1.func1();
    

如果func1正常执行,并且返回值不为Deferred的实例,由于它已经拥有_next,因此接着便是再一次执行_fire方法,这次它的调用者为this._next,即d2:

     if (this._next) this._next._fire(next, value);
      //相当于if(d2)d2.fire(next,value)
      //相当于if(d2)d2.fire("ok",null)
      //相当于d2.call()
    

由于d2的_next为null,func2也没有返回值,程序就此结束。最后提一下,func1与func2是同步执行的,因为它们之间没有用setTimeout隔开。

下回预告:

如果func1与func2异步执行,可以这样写:

      next(function func1(){
        /* 处理 */
      })
      .wait(0)
      .next(function func2(){
        /* 处理 */
      })
    

或者:

        next(function func1(){
          alert("1")
          throw 'error'
        })
        .next(function func2(){
          alert("2")
        })
        .error(function func3(){
          alert("3")
        })
    

这时它执行func1后会跳过func2,执行func3!

posted on 2010-04-19 14:46  司徒正美  阅读(1508)  评论(1)    收藏  举报