Node.js理论实践之《Koa原理浅析》

学习Koa框架以前,不得不提到Express。javascript

Express是一个基于Node.js平台的极简、灵活的 web 应用开发框架,主要基于 Connect 中间件,而且自身封装了路由、视图处理等功能,使用人数众多。java

Koa相对更为年轻,是 Express 原班人马基于 ES6/7 异步流程控制从新开发的框架, 框架自身不包含任何中间件,不少功能须要借助第三方中间件解决,解决了回调地狱和麻烦的错误处理问题。node

  • koa2与koa1的最大区别是koa2实现异步是经过async/await,koa1实现异步是经过generator + co,而express实现异步是经过回调函数的方式。
  • koa2与express 提供的API大体相同,express是大而全,内置了大多数的中间件,更让人省心,koa2不绑定任何的框架,干净简洁,小而精,更容易实现定制化,扩展性好。
  • express是没有提供ctx来提供上下流服务,须要更多的手动处理,express自己是不支持洋葱模型的数据流入流出能力的,须要引入其余的插件。

源码结构

咱们主要学习Koa2,首先看一下它的源码结构: es6

image
koa2的核心文件: application.jscontext.jsrequest.jsresponse.js

  1. application.js:Application(或Koa)负责管理中间件,以及处理请求web

    • application.js是koa的入口文件,它向外导出了建立class实例的构造函数,它继承了events,这样就会赋予框架事件监听和事件触发的能力。 application还暴露了一些经常使用的api,好比toJSON、listen、use等等。
    • listen的实现原理其实就是对http.createServer进行了一个封装,重点是这个函数中传入的callback,它里面包含了中间件的合并,上下文的处理,对res的特殊处理。
    • use是收集中间件,将多个中间件放入一个缓存队列中,而后经过koa-compose这个插件进行递归组合调用这一些列的中间件。
  2. context.js:Context维护了一个请求的上下文环境express

    • koa的应用上下文ctx,其实就一个简单的对象暴露,里面的重点在delegate,这个就是代理,这个就是为了开发者方便而设计的。
    • ctx 主要的功能是代理 request 和 response 的功能,提供了对 request 和 response 对象的便捷访问能力。 好比咱们要访问ctx.response.status可是咱们经过delegate,能够直接访问ctx.status访问到它。
  3. request.js:Request对req作了抽象和封装api

  4. response.js:Response对res作了抽象和封装数组

    这两部分就是对原生的res、req的一些操做了,大量使用es6的get和set的一些语法,去取headers或者设置headers、还有设置body等等。promise

功能模块

koa框架须要实现四个大模块,分别是:缓存

  1. 封装http模块的server、建立Koa类构造函数
  2. 构造request、response、context对象
  3. 中间件机制和洋葱模型的实现
  4. 错误捕获和错误处理

封装http模块的server、建立Koa类构造函数:

经过application.js源码得知,koa的服务器应用和端口监听,其实就是基于node的http.createServer原生代码进行了封装。咱们来简单实现一下这个功能。

let http = require('http');

class Application{    
    constructor() {        
        this.callbackFunc = ()=>{};
    }
    //开启服务器实例并传入callback回调函数
    listen(port) {        
        let server = http.createServer(this.callback());
        server.listen(port);
    }
    //注册中间件和注册回调函数
    use(fn) {
        this.callbackFunc = fn;
    }
    callback() {
        return (req, res) => {
            this.callbackFunc(req, res);
        };
    }
}
module.exports = Application;
复制代码

而后建立demo.js,引入application.js

let Koa = require('./application');
let app = new Koa();
app.use((req, res) => {
    res.writeHead(200);
    res.end('hello world');
});
app.listen(3000, () => {
    console.log('listening on 3000');
});
复制代码

构造request、response、context对象:

request、response两个功能模块分别对node的原生request、response进行了一个功能的封装,使用了getter和setter属性,基于node的对象req/res对象封装koa的request/response对象。

  • request.js 封装了query、header、url、origin、path等,好比ctx.query就是返回url.parse(this.req.url, true).query。
  • response.js 封装了status、body、message等,好比ctx.status就是返回res.statusCode。
  • context.js 再将request和response挂载到了ctx上。
let http = require('http');
let context = require('./context');
let request = require('./request');
let response = require('./response');

createContext(req, res) {       
   let ctx = Object.create(this.context);
   ctx.request = Object.create(this.request);
   ctx.response = Object.create(this.response);
   ctx.req = ctx.request.req = req;
   ctx.res = ctx.response.res = res; 
   return ctx;
}
复制代码

中间件机制和洋葱模型的实现

koa的中间件机制是洋葱模型,多个中间件经过use放进一个数组队列,符合先进后出的原则。而后从外层开始执行,遇到next后进入队列中的下一个中间件,全部中间件执行完后开始回帧,执行队列中以前中间件中未执行的代码部分。

image

示例:

const Koa = require('koa');
const app = new Koa();

//#1
app.use(async (ctx, next) => {
  console.log('中间件 1 进入');
  await next();
  console.log('中间件 1 退出');
});

//#2
app.use(async (ctx, next) => {
  console.log('中间件 2 进入');
  await next();
  console.log('中间件 2 退出');
});

//#3
app.use(async (ctx, next) => {
  console.log('中间件 3');
});

app.listen(3000);
复制代码

访问http://localhost:3000,控制台打印:

中间件 1 进入
中间件 2 进入
中间件 3 
中间件 2 退出
中间件 1 退出
复制代码

当程序运行到await next()的时候就会暂停当前程序,进入下一个中间件,处理完以后才会仔回过头来继续处理。 也就是说,当一个请求进入,#1会被第一个和最后一个通过,#2则是被第二和倒数第二个通过,依次类推。

经过use传进来的中间件是一个回调函数,回调函数的参数是ctx上下文和next,next其实就是控制权的交接棒,next的做用是中止运行当前中间件,将控制权交给下一个中间件, 执行下一个中间件的next()以前的代码,当下一个中间件运行的代码遇到了next(),又会将代码执行权交给下下个中间件,当执行到最后一个中间件的时候,控制权发生反转, 开始回头去执行以前全部中间件中剩下未执行的代码,当最终全部中间件所有执行完后,会返回一个Promise对象,由于咱们的compose函数返回的是一个async的函数,async函数执行完后会返回一个Promise。

function _createNext(middleware, oldNext) {
    return async () => {
        await middleware(ctx, oldNext);
    }
}

compose() {
    return async (ctx) => {
        let next = async () => {
            return Promise.resolve();
        };
        for (let i = this.middlewares.length - 1; i >= 0; i--) {
            let currentMiddleware = this.middlewares[i];
            next = _createNext(currentMiddleware, next);
        }
        await next();
    };
}
复制代码

错误捕获和错误处理

错误处理和捕获,分为中间件的异常Koa框架框架层的异常

前者在application.jscallback方法中,根据es7的规范知道,async返回的是一个promise的对象实例,咱们若是想要捕获promise的错误,只须要使用promise的catch方法,就能够把全部的中间件的异常所有捕获到。 相似这样:

return fn(ctx).then(respond).catch(onerror);
复制代码

后者,经过将koa的构造函数继承events模块,使其具有事件监听on函数和事件触发emit行为的能力。

let EventEmitter = require('events');
class Application extends EventEmitter {}
复制代码

这样,当咱们建立koa实例的时候,就能够加上on监听函数。

let app = new Koa();

app.on('error', err => {
    console.log('error happends: ', err.stack);
});
复制代码
相关文章
相关标签/搜索