本系列旨在通过对co,koa等库源码的研究,进而理解generator在异步编程中的重大作用(ps:所有代码请在node --harmony或者iojs环境中运行)

koa中间件的形式

相信用过koa的小伙伴一定很熟悉下面这段代码

var app = require('koa')(),
    router = require('koa-router')();

app.use(function *(next){
    console.log(1);
    yield next;
    console.log(5);
});
app.use(function *(next){
    console.log(2);
    yield next;
    console.log(4);
});
app.use(function *(){
    console.log(3);
});

app.listen(3000);

当一个请求到达的时候,控制台会依次输出1 2 3 4 5,这就是koa中强大的middleware特性,撇开koa本身,我们来扯扯middleware这东西是怎么实现的

yield *iterator

不熟悉generatoriterator的小伙伴可以先看下阮一峰先生编写的《ES6入门》的generator函数这一节
假设你已经熟悉了generator和iterator,现在我们来看一段代码

function *m1(){
    console.log(1);
    yield *m2Iterator;
    console.log(3);
}

function *m2(){
    console.log(2);
}
var m1Iterator = m1(),
    m2Iterator = m2();

m1Iterator.next();//

上面代码运行后,控制台会依次输出1 2 3,这是因为在m1函数内部,yield后面跟的是一个iteratorm1Iterator.next()调用后,m1函数开始执行。 当m1运行到yield *m2Iterator时,不会暂停,而是直接跳进m2函数执行m2函数内的代码。 由于m2函数中没有yield,因此会一直执行完m2函数中的代码,并返回至m1函数中执行yield *m2Iterator后面的代码。

middleware中间件的雏形

理解了上面的过程后,我们接着看下面这段代码

function *m1(){
    console.log(1);
    yield *m2();
    console.log(5);
}

function *m2(){
    console.log(2);
    yield *m3();
    console.log(4);
}

function *m3(){
    console.log(3);
}

m1().next(); //控制台依次输出1 2 3 4 5

是不是形式跟我们一开始看到的koa的中间件有点接近了?

compose

composekoa里用来实现中间件机制的模块
compose函数最关键的一段代码

while(i--){
    next = middleware[i].call(this, next);
}
yield *next;

这段代码把middleware中传进来的generator数组逆序取出并依次执行,每次执行的时候把上次generator执行返回的iterator当作参数传进去,所有generator执行完之后,调用第一个iterator
因此,在generator中间件函数中,其实yield后面跟的是个iterator,即yield *next
下面用一个例子来说明这个过程

function *m1(next){
    console.log(1);
    yield *next;
    console.log(5);
}

function *m2(next){
    console.log(2);
    yield *next;
    console.log(4);
}

function *m3(){
    console.log(3);
}

var gen = compose([m1, m2, m3]),
    it = gen();
//compose([m1, m2, m3])()可以想象成它做了这么一些事(伪代码):
//1 var it3 = m3();
//2 var it2 = m2(it3);
//3 var it1 = m1(it2);
//4 gen = function(){
//          yield *it1;  
//        }

it.next(); //控制台依次输出1 2 3 4 5

现在就很像koa的中间件的写法了。唯一的差别是koa中使用的是yeild next而我们这里用的是yield *next 其实在koayeild nextyeild *next效果是等价的,这主要得益于co库,我们以后再来好好扯扯这小玩意~