Koajs开发最佳实践
发布在koa小教室2014年11月13日view:9849
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

Koajs开发最佳实践

如今,越来越多的工程师已经开始将Koa这款简单强大的node框架作为web开发的第一选择。如果你刚接触Koa没多久,你可能会觉得其中使用的generator不如使用Express框架来的直观,但是当你开始慢慢理解熟悉Koa之后,一定会对这款框架带来的强大的可定制性感到欲罢不能。在Koa的世界中,不再有神秘的黑盒子,你可以轻松的掌控一切。在本文中,我们不打算逐个详细的介绍Koa的API,而是会对Koa中常见的一些编程工作,例如编写中间件等重要部分的最佳实践做一些讲解。

编写中间件

Koa的中间件其实就是非常普通的函数,唯一不同的是它返回的是一个GeneratorFunction,同时也接受另一个。当一个中间件被一个”上游”的中间件运行时,你需要手动的使用yield来往下继续到”下游”的中间件。

例如,如果你想要弄清楚一个通过Koa处理的请求究竟花费了多长时间,并在响应头部中添加了一个X-Response-Time字段来表明这个时间,那么中间件代码应该如下所示:

function *responseTime(next) {
  var start = new Date;
  yield next;
  var ms = new Date - start;
  this.set('X-Response-Time', ms + 'ms');
}

app.use(responseTime);

下面的写法和上面的写法是等价的,不同的是下面的代码使用了内联的方式:

app.use(function *(next){
  var start = new Date;
  yield next;
  var ms = new Date - start;
  this.set('X-Response-Time', ms + 'ms');
});

如果你有前端开发的经验,你可以很容易的将yield同前端DOM中的事件冒泡捕获机制联系起来,你可以将yield next前面所有的代码想象成是事件的”捕获”阶段,而之后的代码想象成事件的”冒泡”阶段。下面的这张gif图很形象的展示了ES6中的generator的运行过程:

#图片1

上面gif图清楚的表明了koa处理一个请求的12个阶段:

  1. 创建一个date变量来追踪请求持续时间;
  2. 将控制权yield到下一个中间件;
  3. 创建另一个date来追踪相应时间;
  4. 将控制权yield到下一个中间件;
  5. 由于contentLength只在响应中可用,因此不做任何处理直接yield;
  6. 将上游yield到Koa的空中间件;
  7. 如果path是’/’,设置响应体;
  8. 设置响应为”Hello World”;
  9. 如果没有响应体则忽略Content-Length的设置;
  10. 设置Content-Length
  11. 输出日志行;
  12. 在响应之前设置X-Response-Time
  13. 让Koa去处理相应;

在上面的步骤6中,Koa似乎把控制权yield到了一个莫名其妙的地方 – 其实它是将控制流yield到了Koa中的空生成器中。正是因为如此,每一个中间件都能使用相同的API,同时也能放在任何位置。如果你移除了yield next;那么代码并不会报错,但是这段代码似乎就没有什么实际作用了。

例如,下面的这段代码并不会报错:

app.use(function *response(){
  if ('/' != this.url) return;
  this.body = 'Hello World';
});

下面我们将介绍的是创建Koa中间件的最佳实践。

中间件最佳实践

中间件选项

当你在创建一个中间件时,一般来说需要将这个中间件包含在一个函数中,这个函数应该能够接收一些选项,这样你可以允许中间件的用户扩展这个中间件的功能。即使这个中间件不接受任何选项,这样一来也可以保证代码的风格一致。

在下面的代码中,我们的logger中间件将会接收一个自定义的format字符串,并根据这个字符串返回相应的中间件:

function logger(format) {
  format = format || ':method ":url"';

  return function *(next){
    var str = format
      .replace(':method', this.method)
      .replace(':url', this.url);

    console.log(str);

    yield next;
  }
}

app.use(logger());
app.use(logger(':method :url'));

命名中间件

给中间件命名并不是必须的,但是当你在调试代码时,这将非常有用。

function logger(format) {
  return function *logger(next){

  }
}

合并多个中间件

有时你可能会想要将多个中间件合并为一个中间件来达到易用的目的。这是,你可以使用.call(this,next)将这些中间件链接起来,然后返回另一个函数来yield。

function *random(next) {
  if ('/random' == this.path) {
    this.body = Math.floor(Math.random()*10);
  } else {
    yield next;
  }
};

function *backwards(next) {
  if ('/backwards' == this.path) {
    this.body = 'sdrawkcab';
  } else {
    yield next;
  }
}

function *pi(next) {
  if ('/pi' == this.path) {
    this.body = String(Math.PI);
  } else {
    yield next;
  }
}

function *all(next) {
  yield random.call(this, backwards.call(this, pi.call(this, next)));
}

app.use(all);

相应中间件

如果一个中间件想要相应请求并且绕过下游,你可以简单的省略yield next。在路由中间件中这很常见,但是你可以在任何类型的中间件中做这件事。例如,下面的例子中的相应将会是two,但是所有三个中间件都会被执行,three中间件虽然对相应没有实际影响,但是它也有影响响应的能力。

app.use(function *(next){
  console.log('>> one');
  yield next;
  console.log('<< one');
});

app.use(function *(next){
  console.log('>> two');
  this.body = 'two';
  yield next;
  console.log('<< two');
});

app.use(function *(next){
  console.log('>> three');
  yield next;
  console.log('<< three');
});

而下面的例子中,第二个中间件忽略了yield next,因此响应依然是two,但是第三个中间件将会被忽略:

app.use(function *(next){
  console.log('>> one');
  yield next;
  console.log('<< one');
});

app.use(function *(next){
  console.log('>> two');
  this.body = 'two';
  console.log('<< two');
});

app.use(function *(next){
  console.log('>> three');
  yield next;
  console.log('<< three');
});

当最后一个中间件执行yield next;时,它最终会被yield到一个空函数,这允许中间件在代码的任何地方都能被正确的合并。

异步操作

Co是Koa用来进行生成器代理的基础,它允许我们以串行的方式编写无阻塞的代码。例如在下面的中间件中,我们会从./docs中付读取文件名,然后并行的读取每一个markdown文件的内容,最后将它们连接起来。

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

app.use(function *(){
  var paths = yield fs.readdir('docs');

  var files = yield paths.map(function(path){
    return fs.readFile('docs/' + path, 'utf8');
  });

  this.type = 'markdown';
  this.body = files.join('');
});

调试Koa

Koa中内置了一些库,其中一个debug库就能够支持在DEBUG环境下提供简单的条件日志。

例如,我们可以动过传递DEBUG=koa*来查看所有的koa相关调试信息。

$ DEBUG=koa* node --harmony examples/simple
  koa:application use responseTime +0ms
  koa:application use logger +4ms
  koa:application use contentLength +0ms
  koa:application use notfound +0ms
  koa:application use response +0ms
  koa:application listen +0ms

因为JavaScript不允许在运行时定义函数,你可以设置一个中间件的名字叫做._name。当你不能控制中间件名字时,这非常有用。例如:

var path = require('path');
var static = require('koa-static');

var publicFiles = static(path.join(__dirname, 'public'));
publicFiles._name = 'static /public';

app.use(publicFiles);

这样依赖,你在调试代码时看到的不仅仅是static了,而是下面经过自定义的调试信息:

  koa:application use static /public +0ms

本文参考自Koa guide,原文地址https://github.com/koajs/koa/blob/master/docs/guide.md

评论
发表评论
2年前
添加了一枚【评注】:很不多,也说不上来哪有问题
3年前

NICE.

WRITTEN BY
张小俊128
Intern in Baidu mobile search department。认真工作,努力钻研,期待未来更多可能。
TA的新浪微博
PUBLISHED IN
koa小教室

koa框架的开发技巧

我的收藏