Koa

next generation web framework for node.js

  • 简介
  • Koa如何使用
  • 深入解析
  • 总结

简介

  • 提供异步解决方案
  • 更灵活的中间件
  • 异常处理
  • 更小,更优雅...

Express

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

app.use(function (req, res, next) {
  // ...
  next();
})

app.use(function (req, res) {
  res.send('Hello World!');
});

app.listen(3000);

req, res, next

Koa

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

// 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);

this, yield, next

  • 简介
  • Koa如何使用
  • 深入解析
  • 总结

初始化App

var app = koa();

常用方法

  • app.listen
  • app.use(function)

Hello World

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);

Koa中最最重要的两个部分

  • 中间件 - Middleware
  • 上下文 - Context

中间件

例子

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

app.use(function* (next) {
  console.log('f1: pre next');
  yield next;
  console.log('f1: post next');
});

app.use(function* (next) {
  console.log('  f2: pre next');
  yield next;
  console.log('  f2: post next');
});

app.use(function* (next) {
  console.log('  f3: pre next');
  yield next;
  console.log('  f3: post next');
});

app.use(function* (next) {
  console.log('hello world')
  this.body = 'hello world';
});

app.listen(3000);

上下文 - Context

  • this
    • Request
    • Response
    • req
    • res
    • app
    • ...

Request & Response

例子

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

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

app.listen(3000);

异常处理

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

app.use(function *(next){
  try {
    yield next;
  } catch (err) {
    this.status = err.status || 500;
    this.body = 'Something exploded';
  }
});

app.use(function *(){
  throw new Error('boom boom');
});

app.listen(3000);

异常处理2

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

app.use(function *(){
  var userID = this.query.userID;

  try {
    this.body = yield user.findById(userID);
  } catch (err) {
    this.status = err.status || 403;
    this.body = 'incorrect parameter';
    this.app.emit('error', err, this);
  }
});

app.listen(3000);

异步

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

app.use(function *(){
  function delay(interval) {
    return function (done) {
      setTimeout(function () {
        done(null, 'delay done');
      }, interval);
    };
  }

  var time = Date.now();

  console.log('收到请求');
  var body = yield delay(5000);
  console.log(Date.now() - time);

  this.body = body;
});

app.listen(3000);

异步2

app.use(function *(){
  // 先获取购物车中的数据列表
  var cart = yield cart.findByUserId(userID);

  // 在获取购物车中第一个物品的详细信息
  this.body = yield commodity.findById(cart[0].id);
});

并发

app.use(function *(){
  var userInfo = cart.findByUserId(userID);
  var cart = cart.findByUserId(userID);

  this.body = yield {userInfo: userInfo, cart: cart};
});
this.body = yield [userInfo, cart];
  • 简介
  • Koa如何使用
  • 深入解析
  • 总结

深入解析

两个重要的部分

  • 中间件 - Middleware
  • 上下文 - Context

中间件 - Middleware

  • Generator
  • Promise
  • Co

中间件 - 流程图

Co

处理异步(与async有点像)

  • 解放双手,自动执行.next
  • 监听 yield 后面的promise
  • co返回promise(return promise)

koa充分利用co特性,通过3个步骤实现中间件逻辑

  1. 通过一个叫做koa-compose的包来把中间件关联在一起,把多个中间件组合成一个中间件
  2. 把组合后的中间件丢到co中去执行
  3. yield 链

yield链必须满足两个条件

  • 每个中间件需要返回promise
  • 中间件内部可以监听promise

yield 链

伪代码


new Promise(function(resolve, reject) {
  // 我是中间件1
  yield new Promise(function(resolve, reject) {
    // 我是中间件2
    yield new Promise(function(resolve, reject) {
      // 我是中间件3
      yield new Promise(function(resolve, reject) {
        // 我是body
      });
      // 我是中间件3
    });
    // 我是中间件2
  });
  // 我是中间件1
});

co的原理

接收一个参数


function *() {
  yield *m1(m2(m3(noop())))
}

next是下一个中间件


app.use(function* f1(next) {
  console.log('f1: pre next');
  yield next;
  console.log('f1: post next');
});

app.use(function* f2(next) {
  console.log('  f2: pre next');
  yield next;
  console.log('  f2: post next');
});

app.use(function* f3(next) {
  console.log('  f3: pre next');
  yield next;
  console.log('  f3: post next');
});

app.use(function* (next) {
  this.body = 'hello world';
});

co源代码片段

function co(gen) {
  return new Promise(function(resolve, reject) {
    onFulfilled();

    function onFulfilled(res) {
      var ret;
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }

    function next(ret) {

      if (ret.done) return resolve(ret.value);
      var value = toPromise.call(ctx, ret.value);

      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);

      return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
        + 'but the following object was passed: "' + String(ret.value) + '"'));
    }
  });
}

co 主要其实干了两件事

  • 执行gen.next
  • 把yield返回的内容转换成promise并监听

一个重要的细节 - onFulfilled的执行机制

  • 在第一次执行co的时候执行
  • 在监听的promise执行成功的时候执行

例子

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

app.use(function* f1(next) {
  console.log('f1: pre next');
  yield next;
  console.log('f1: post next');
  yield next;
  console.log('f1: done');
});

app.use(function* f2(next) {
  console.log('  f2: pre next');
  yield next;
  console.log('  f2: post next');
  yield next;
  console.log('  f2: done');
});

app.use(function* f3(next) {
  console.log('  f3: pre next');
  yield next;
  console.log('  f3: post next');
  yield next;
  console.log('  f3: done');
});

app.use(function* (next) {
  console.log('hello world')
  this.body = 'hello world';
});

app.listen(3000);

co流程图

上下文 - Context

koa源代码片段

function(req, res){
  res.statusCode = 404;
  var ctx = self.createContext(req, res);

  fn.call(ctx).then(function () {
    respond.call(ctx);
  }).catch(ctx.onerror);
}

/**
 * Initialize a new context.
 *
 * @api private
 */

app.createContext = function(req, res){
  var context = Object.create(this.context);
  var request = context.request = Object.create(this.request);
  var response = context.response = Object.create(this.response);
  context.app = request.app = response.app = this;
  context.req = request.req = response.req = req;
  context.res = request.res = response.res = res;
  request.ctx = response.ctx = context;
  request.response = response;
  response.request = request;
  context.onerror = context.onerror.bind(context);
  context.originalUrl = request.originalUrl = req.url;
  context.cookies = new Cookies(req, res, this.keys);
  context.accept = request.accept = accepts(req);
  context.state = {};
  return context;
};

谢谢