学习Koa框架以前,不得不提到Express。javascript
Express
是一个基于Node.js平台的极简、灵活的 web 应用开发框架,主要基于 Connect 中间件,而且自身封装了路由、视图处理等功能,使用人数众多。java
Koa
相对更为年轻,是 Express 原班人马基于 ES6/7 异步流程控制从新开发的框架, 框架自身不包含任何中间件,不少功能须要借助第三方中间件解决,解决了回调地狱和麻烦的错误处理问题。node
async/await
,koa1实现异步是经过generator + co
,而express实现异步是经过回调函数
的方式。咱们主要学习Koa2,首先看一下它的源码结构: es6
application.js
、
context.js
、
request.js
、
response.js
。
application.js:Application(或Koa)负责管理中间件,以及处理请求web
koa的入口文件
,它向外导出了建立class实例的构造函数,它继承了events
,这样就会赋予框架事件监听和事件触发
的能力。 application还暴露了一些经常使用的api,好比toJSON、listen、use等等。传入的callback
,它里面包含了中间件的合并,上下文的处理,对res的特殊处理。context.js:Context维护了一个请求的上下文环境express
重点在delegate
,这个就是代理,这个就是为了开发者方便而设计的。request.js:Request对req
作了抽象和封装api
response.js:Response对res
作了抽象和封装数组
这两部分就是对原生的res、req的一些操做了,大量使用es6的get和set的一些语法,去取headers或者设置headers、还有设置body等等。promise
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两个功能模块分别对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后进入队列中的下一个中间件,全部中间件执行完后开始回帧,执行队列中以前中间件中未执行的代码部分。
示例:
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.js
的callback
方法中,根据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);
});
复制代码