Koa2源码阅读 之 入口文件

最近在美团点评实习,接触的项目都是使用前后端分离技术。中间层使用Koa2作为跨域请求转发。
在学习Koa2的过程中,发现Koa2的源码只有4个文件,四个文件的合起来的代码都没超过2000行,
就决定把Koa2的源码看一遍,这里也做一个整理.

源码主线

阅读源码就要先找出框架的主线
从Git上下载Koa的源码后,打开四个源码文件:application.js, context.js, request.js, response.js
从application.js的导入模块的如下代码,

const response = require('./response');
const context = require('./context');
const request = require('./request');

很容易可以看出application.js是整个源码项目的入口文件.

application.js中的语法糖解析

整个模块导出的类是Application类。类中包含了Koa2中文档所描述的各种语法糖。这些语法糖都是对 Node.js功能的扩展封装。这里只简单说明下Application中比较重要的语法糖,没有做全部介绍。代码都比较优雅好理解,可以自己查阅。

  1. 从Application类开始看起

    class Application extends Emitter{
    //......
    }

    整个Application都继承Node.js的events模块.所有的异步 I/O 操作在完成时都会发送一个事件到事件队列.
    Emitter 的核心就是事件触发与事件监听器功能的封装.

  2. 构造函数

    constructor() {
    super();
    this.proxy = false;
    this.middleware = [];
    this.subdomainOffset = 2;
    this.env = process.env.NODE_ENV || 'development';
    this.context = Object.create(context);
    this.request = Object.create(request);
    this.response = Object.create(response);
    }

    从构造函数中,就可以知道在Koa2中新建一个app.可以在app中进行设置的app.env, app.proxy的默认设置都在这构造函数中

  3. use中间件函数

    use(fn) {
    if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
    if (isGeneratorFunction(fn)) {
    deprecate('Support for generators will be removed in v3. ' +
    'See the documentation for examples of how to convert old middleware ' +
    'https://github.com/koajs/koa/blob/master/docs/migration.md');
    fn = convert(fn);
    }
    debug('use %s', fn._name || fn.name || '-');
    this.middleware.push(fn);
    return this;
    }

    每新增加一个中间件,都会添加到this.middleware中

  4. callback函数

    callback() {
    const fn = compose(this.middleware);
    if (!this.listeners('error').length) this.on('error', this.onerror);
    const handleRequest = (req, res) => {
    res.statusCode = 404;
    const ctx = this.createContext(req, res);
    const onerror = err => ctx.onerror(err);
    const handleResponse = () => respond(ctx);
    onFinished(res, onerror);
    return fn(ctx).then(handleResponse).catch(onerror);
    };
    return handleRequest;
    }

    在Koa2中,app.callback()是返回一个适合 http.createServer() 方法的回调函数用来处理请求。
    代码中的compose()函数则是出自koa-compose中的代码
    源代码如下:

    function (context, next) {
    // last called middleware #
    let index = -1
    return dispatch(0)
    function dispatch (i) {
    if (i <= index)
    return Promise.reject(new Error('next() called multiple times'))
    index = i
    let fn = middleware[i] //调用下一个middleware
    if (i === middleware.length) fn = next
    if (!fn) return Promise.resolve()
    try {
    //在middleware中传递next方法
    return Promise.resolve(fn(context, function next () {
    return dispatch(i + 1)
    }))
    } catch (err) {
    return Promise.reject(err)
    }
    }
    }

    compose函数实际上就是dispatch的递归调用。还记得app是如何添加异步中间件的吗

    async function (ctx, next) {
    const start = new Date();
    await next();
    const ms = new Date() - start;
    ctx.set('X-Response-Time', `${ms}ms`);
    }

    通过next()执行到下一个中间件。这个compose()返回一个Promise对象一致。

    return Promise.resolve(fn(context, function next () {
    return dispatch(i + 1)
    }))

    callback函数中创建了ctx,ctx代理了request和response的功能,提供更加快捷方便的访问。
    这里就不在过多解释

context.js, request.js, response.js这三个文件,这里就不再做详细解释。
context.js中的Proto模块主要使用了delegates模块的代理request和response的功能。
而request.js和response.js则是对原始Node.js中request和response的封装和扩展。