最简Koa.js实现

前言

从上一章可以看到最简单的中间件式HTTP服务的实现,底层是基于回调嵌套去处理中间件队列。

  /**
   * 中间件总回调方法
   */
  callback() {
    let that = this;

    if (this.listeners('error').length === 0) {
      this.on('error', this.onerror);
    }

    const handleRequest = (req, res) => {
      let context = that.createContext(req, res);
      this.middleware.forEach((cb, idx) => {
        try {
          cb(context);
        } catch (err) {
          that.onerror(err);
        }

        if (idx + 1 >= this.middleware.length) {
          if (res && typeof res.end === 'function') {
            res.end();
          }
        }
      });
    };
    return handleRequest;
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

但是中间件越多,回调嵌套越深,代码的可读性和可扩展性就很差,所以这时候把回调嵌套转化成 Promise + async/await ,这个时候就转变成最简单的Koa.js实现。

必要条件

  • 通过上下文赋值可代替 res.end()
  • 洋葱模型的中间件机制

源码实现

  • demo源码

https://github.com/chenshenhai/koajs-design-note/tree/master/demo/chapter-01-07open in new window

  • 最简Koa.js 实现
const http = require('http');
const Emitter = require('events');
// 注意:这里的compose是前几章的中间件引擎源码
const compose = require('./../compose');

/**
 * 通用上下文
 */
const context = {
  _body: null,

  get body() {
    return this._body;
  },

  set body(val) {
    this._body = val;
    this.res.end(this._body);
  }
};

class SimpleKoa extends Emitter {
  constructor() {
    super();
    this.middleware = [];
    this.context = Object.create(context);
  }

  /**
   * 服务事件监听
   * @param {*} args
   */
  listen(...args) {
    const server = http.createServer(this.callback());
    return server.listen(...args);
  }

  /**
   * 注册使用中间件
   * @param {Function} fn
   */
  use(fn) {
    if (typeof fn === 'function') {
      this.middleware.push(fn);
    }
  }

  /**
   * 中间件总回调方法
   */
  callback() {

    if (this.listeners('error').length === 0) {
      this.on('error', this.onerror);
    }

    const handleRequest = (req, res) => {
      let context = this.createContext(req, res);
      let middleware = this.middleware;
      // 执行中间件
      compose(middleware)(context).catch(err => this.onerror(err))
    };
    return handleRequest;
  }

  /**
   * 异常处理监听
   * @param {EndOfStreamError} err
   */
  onerror(err) {
    console.log(err);
  }

  /**
   * 创建通用上下文
   * @param {Object} req
   * @param {Object} res
   */
  createContext(req, res) {
    let context = Object.create(this.context);
    context.req = req;
    context.res = res;
    return context;
  }
}

module.exports = SimpleKoa;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
  • 执行例子
const SimpleKoa = require('./index');

const app = new SimpleKoa();
const PORT = 3001;

app.use(async ctx => {
  ctx.body = '<p>this is a body</p>';
});

app.listen(PORT, () => {
  console.log(`the web server is starting at port ${PORT}`);
});

1
2
3
4
5
6
7
8
9
10
11
12
13
上次更新: 11/1/2023, 3:16:38 AM
Contributors: zhangningle