初识Koa(二)

Published: by Creative Commons Licence

初识Koa(二)

上一节讲到了generators,可以用同步的代码编写风格来跑异步的代码了,同步代码的编写方式更容易理解(异步代码里那可恶的callback)

这一节,我们就来看看Koa框架会给我们的代码编写带来哪些变化。

首先我们介绍一下co模块,co是基于generator的流程控制模块,还记得上一节的代码吗?还是贴在下面

// First part
var thunkify = require('thunkify');
var fs = require('fs');
var read = thunkify(fs.readFile);

// Second part
function *bar () {
  try {
    var x = yield read('input.txt');
  } catch (err) {
    console.log(err);
  }
  console.log(x);
}

// Third part
var gen = bar();
gen.next().value(function (err, data) {
  if (err) {
    gen.throw(err);
  }
  gen.next(data.toString());
});

通过co,就不用手动去执行generator方法了,唯一要做的就是将generator函数作为参数传给co,代码如下:

var co = require('co');
var thunkify = require('thunkify');
var fs = require('fs');

var read = thunkify(fs.readFile);

co(function *bar () {
  try {
    var x = yield read('input.txt');
  } catch (err) {
    console.log(err);
  }
  console.log(x);
})();

然后执行co函数,一切神奇般地运行起来。这是怎么做到的呢?其实co就是帮你调用generator ,免除你的手动工作。在co中,yield后面目前可以跟以下几种类型:

  • thunks (function)
  • array (将会并行执行)
  • objects (将会并行执行)
  • generators
  • generator functions
  • promises

我们已经知道了thunks是如何工作的,现在来看看其他类型吧

并行执行

var read = thunkify(fs.readFile);

co(function *() {
  // 3 concurrent reads
  var reads = yield [read('input.txt'), read('input.txt'), read('input.txt')];
  console.log(reads);

  // 2 concurrent reads
  reads = yield { a: read('input.txt'), b: read('input.txt') };
  console.log(reads);
})();

如果在yield后面是array类型或者object类型,那么将会并行执行。需要注意的是数组或对象的成员必须是thunks或者generators,还可以相互嵌套。如下所示:

var read = thunkify(fs.readFile);

co(function *() {
  var a = [read('input.txt'), read('input.txt')];
  var b = [read('input.txt'), read('input.txt')];

  // 4 concurrent reads
  var files = yield [a, b];

  console.log(files);
})();

你还可以在调用了thunk之后再来置于yield之后,如下所示:

var read = thunkify(fs.readFile);

co(function *() {
  var a = read('input.txt');
  var b = read('input.txt');

  // 2 concurrent reads
  console.log([yield a, yield b]);

  // or

  // 2 concurrent reads
  console.log(yield [a, b]);
})();

现在你应该掌握了generators的原理了,知道了如何使用它来完成异步流。现在我们再来了解一下Koa本身。

Koa

始终记住一点,每一个koa模块应该只做一件事。

Application

var koa = require('koa');
var app = koa();

创建一个Koa app只需要调用依赖的模块koa就可以了。koa()返回的对象可以包含一组generators(或者说middlewares)。

Cascading (级联)

Middleware(中间件),在Koa中的表现就是用来处理请求。所谓级联,是指由一系列的中间件构成的控制流。在web开发中,这会非常有用。Koa使用generators来实现级联,等下游的generator执行完,控制流再回到上游的generator。给use方法传递一个generator function来向控制流中添加generator。看看下面这段代码为什么依次打印A,B,C,D,E。

app.use(function *(next) {
  console.log('A');
  yield next;
  console.log('E');
});

app.use(function *(next) {
  console.log('B');
  yield next;
  console.log('D');
});

app.use(function *(next) {
  console.log('C');
});

app.listen(3000);

当一个请求进来时,请求沿着中间件,一层层往下走。所以在上面的代码中,请求启动了第一个中间件,于是打印A,然后遇到了yield nex。当一个中间件遇到yield next的时候,它将会进入到下个中间件中。所以接着打印B,然后又遇到了yield next,进入下个中间件,打印C。打印完C后,发现没有多余的中间件了,于是开始往回走,回到上个中间件,就是一个出栈的操作一样。然后打印D,回到上个中间件,打印E。