2009年6月30日星期二

写个 JavaScript 异步调用框架 (Part 5 - 链式实现)

在上一篇文章里面,我们为异步调用框架设计了一种链式调用方式,来增强异步调用队列的代码可读性,现在我们就来编写实现这部分功能的代码。

调用入口

链式调用存在Async.go方法和Async.chain方法两个入口,这两个入口本质上是一致的,只是Async.chain方法在调用时先不提供初始参数,而Async.go方法在调用时提供了初始参数并启动异步调用链。

Async.chain = function() {
  var chain = new Async.Operation({ chain: true });
  return chain;
};

Async.go = function(initialArgument) {
  return Async.chain().go(initialArgument);
}


在这里我们可以看到,链式调用本身也是一个Async.Operation,链式调用所需的go方法和next方法都是在Async.Operation上面做的扩展,并且这个扩展不会很难,这将在下一小节说明。

扩展方法

我们都知道,通过addCallback方法添加的回调函数是会被逐一执行的,至少同步函数如此,因此我们可以用Async.Operation的这一特性来维护异步调用队列,前提是我们为它加上对异步调用进行队列的支持。

对于异步调用进行队列的支持,我们稍后再来处理,首先我们利用现成的addCallback方法和yield方法扩展出go方法和next方法。

this.go = function(initialArgument) {
  return this.yield(initialArgument);
}

this.next = function(nextFunction) {
  return this.addCallback(nextFunction);
};


实际上,go方法和next方法直接调用的正是yield方法和addCallback方法。go方法的语义与yield方法一样,传递一个参数给Async.Operation实例,并且启动调用队列。同时,next方法的语义和addCallback方法,添加一个调用到队列的末端。

异步队列

如何才能让原本仅支持同步的队列变得也支持异步?这需要检测队列中的每一个调用的返回,如果返回类型为Async.Operation,我们知道是异步调用,从而使用特殊的方法等它执行完后再执行下去。

callbackResult = callback(self.result);
self.result = callbackResult;
if (callbackResult && callbackResult instanceof Async.Operation) {
  innerChain = Async.chain();
  while (callbackQueue.length > 0) {
    innerChain.next(callbackQueue.shift());
  }
  innerChain.next(function(result) {
    self.result = result;
    self.state = "completed";
    self.completed = true;
    return result;
  });
  callbackResult.addCallback(function(result) {
    self.result = result;
    innerChain.go(result);
  });
}


如果调用返回了一个Async.Operation实例,我们就利用它自身的addCallback方法帮我们执行队列中余下的调用。准确来说,是我们构造了一个新的调用链,把队列余下的调用都转移到新的调用链上,然后让当前异步调用在回调中启动这个新的调用链。

此外还有一些地方我们需要略作修改,以兼容新的异步调用队列的。例如result、state、completed的状态变更,在链式调用中是有所不同的。

小结

我们在原有的Async.Operation上略作修改,使得它支持异步调用队列,完整的代码看这里:支持链式调用的异步调用框架Async.Operation

现在我们已经拥有了一个功能强大的Async.Operation,接下来我们就要看看如何将它投入到更多常见的使用模式中去,如果你不希望错过相关讨论的话,欢迎订阅我的博客:

6 条评论:

  1. 你的这个框架对我很有用,我最近正好有个需求和你的设计类似。
    问个问题:如何将一个Ajax请求作为队列的其中一步呢?

    回复删除
  2. 用jQuery举例,你可以把对$.get的调用变成一个Async.Operation:

    Ajax.get = function(url, data) {
    var operation = new Async.Operation();
    $.get(url, data, function(result) { operation.yield(result); }, "json");
    return operation;
    };

    之后,你就可以将对Ajax.get的调用放到匿名函数中然后next添加到队列中。

    详细的使用实例会在下一篇文章中给出。

    回复删除
  3. 原来如此,谢谢!
    期待你的下一篇文章。

    回复删除
  4. 我一直不太理解yield的含义,为什么这么命名?能否解释一下?

    回复删除
  5. 其实本来我想到的是return,但return是关键字,不能用作方法名称。后来我想到yield,在很多语言中yield是一个类似与return的关键字,就是将控制权移交给调用方。

    yield和return不同的是,yield之后程序会保存函数当前的调用状态,下一次再调用这个函数会在yield的下一句开始执行;而return结束的函数下一次调用还是从头开始执行。

    回复删除
  6. 谢谢你的解释,刚才Google了一下发现C#就有这个关键字,用于在迭代器块中用于向枚举数对象提供值或发出迭代结束信号。

    回复删除