Skip to content

Latest commit

 

History

History
286 lines (219 loc) · 6.38 KB

learn koa2--middleware.md

File metadata and controls

286 lines (219 loc) · 6.38 KB

Table of Contents generated with DocToc

learn koa2--middleware

玩过express,但没接触过Koa1。虽然Koa2发布到现在,已有的文档资料相比于1而言还缺少许多,但鉴于Koa2对ES6的良好支持和新特性,还是直接干脆的上Koa2比较好吧?因此,下文全部都是基于Koa2学习整理而成的。

Koa真的是一个非常简洁的框架,只是给开发者提供了一个基本的解决方案。之后无论是Router还是Controller或者Model都需要自己去实现,这也就有了很大的扩展空间。

Install & Basical

新建项目,初始化npm,然后准备koa的安装和初始化:

$ npm install koa@next --save
# 运行koa
$ npm install nodemon --save

为了能够顺畅的使用我们心爱的ES6语法,需要安装一下polyfill

$ npm install babel-register babel-plugin-transform-async-to-generator --save
$ npm install babel-polyfill --save-dev
$ npm install babel-preset-es2015 --save-dev
$ npm install babel-preset-stage-0 --save-dev

顺便在根目录建立.babelrc文件:

{
  "presets": [
    "es2015",
    "stage-0"
  ]
}

建立初始化文件:

// app/app.js
import Koa from 'koa';
const app = new Koa();

app.use((ctx, next) => {
  const start = new Date;
  console.log('app 1')
  return next().then(() => {
    const ms = new Date - start;
    console.log('app 1 callback')
    console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
  });
});

app.use(async (ctx, next) => {
  const start = new Date;
  console.log('app 2')
  await next();
  const ms = new Date - start;
  console.log('app 2 callback')
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});

app.use(ctx => {
  console.log('app 3')
  ctx.body = "Hello Koa";
});

app.listen(7000);

export default app;
// index.js
require("babel-core/register");
require("babel-polyfill");
require("./app/app.js");
"scripts": {
    "start": "./node_modules/.bin/nodemon index.js"
}

之后运行:

$ npm start

打开localhost:7000,就能看见Hello Koa喽

Middleware

middleware(中间件)始终是Koa中的一个核心概念。在middleware中,中间件由外而内的相互嵌套,执行完毕之后再由内到外的依次执行回调。不得不盗张官方的图:

middleware

在Koa1中,中间件的表现形式是这样的:

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

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

// logger
app.use(function *(next){
  var start = new Date;
  yield next;
  var ms = new Date - start;
  console.log('%s %s - %s', this.method, this.url, ms);
});

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

app.listen(3000);

三个作用的function:x-response-timeloggerresponse,每个function都是中间件,它们在运行到yield next时,会跳到下一个中间件里运行,直到最后一个没有yield next的中间件(response),在那里执行完毕之后,再从下往上的返回各个中间件,并执行yield next之后的语句,用我的渣配图来表示的话就是这样的:

middleware

因此,middleware的顺序很重要。

在Koa2中,我们可以使用async/await语法,所以中间件可以写成这样:

app.use((ctx, next) => {
  const start = new Date;
  console.log('app 1')
  return next().then(() => {
    const ms = new Date - start;
    console.log('app 1 callback')
    console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
  });
});

app.use(async (ctx, next) => {
  const start = new Date;
  console.log('app 2')
  await next();
  const ms = new Date - start;
  console.log('app 2 callback')
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});

app.use(ctx => {
  console.log('app 3')
  ctx.body = "hello KOA";
});

每一个中间件在await next()之前会进入下一个中间件,全部执行完毕之后又会向上执行回调。运行项目,可以打印出log如下:

app 1
app 2
app 3
app 2 callback
GET / - 3ms
app 1 callback
GET / - 7ms

Middleware best practices

  • 对于一个公共的middleware,将它用方法包裹起来,并在方法中进行类型检查

例如,一个打印log的middleware

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

  return async (ctx, next) => {
    const str = format
      .replace(':method', ctx.method)
      .replace(':url', ctx.url);

    console.log(str);

    await next();
  };
};

app.use(logger());
app.use(logger(':method :url'));
  • 命名一个middleware,有利于debug
const logger = (format) => {
  return async logger(ctx, next) => {
    // print log
  };
};
$ npm install koa-compose --save

Usage:

compose([a, b, c, ...]);

example:

import compose from 'koa-compose';

async random = (ctx, next) => {
  if ('/random' == ctx.path) {
    ctx.body = Math.floor(Math.random() * 10);
  } else {
    await next();
  }
};

async backwards = (ctx, next) => {
  if ('/backwards' == ctx.path) {
    ctx.body = 'sdrawkcab';
  } else {
    await next();
  }
}

async pi = (ctx, next) => {
  if ('/pi' == ctx.path) {
    ctx.body = String(Math.PI);
  } else {
    await next();
  }
}

const all = compose([random, backwards, pi]);

app.use(all);

Middleware in usage

利用middleware,可以便捷的捕获到中间件在传递过程中,由下游回调至上游过程中的错误:

app.use((ctx, next) => {
  try {
    next();
  } catch (err) {
    ctx.response.status = err.status || 500;
    ctx.body = err.message;
  }
});

app.use((ctx, next) => {
  throw new Error('some error');
});