Koa深入浅出
简介
- 定义:
Koa
是Express
的原班开发人马使用ES2015
中的新特性(主要是Generator
)重新打造的新的Web
框架——Koa,Koa的初衷就是彻底解决在Node
Web开发中的异步问题,
- 历史:
Koa
的发展存在Koa1.x
和Koa2
两个阶段,两者之间的区别在于Koa2
使用了ES2017
中async
方法来处理中间件的调用(Koa1.x使用的是generator
),该特性已经在v7.6.0
之后的Node
版本中提供原生支持。
使用例子
- 整体步骤
- 引入Koa模块,创建app对象
- 挂载中间件
- 调用listen方法监听端口
const Koa = require('Koa');
const app = new Koa();
//加载一些中间件
app.use(...);
app.use(....);
app.use(.....);
app.listen(3000);
流程简述
- 通过挂载的方式(
app.use
),将若干中间件函数推入一个数组中(middlewares
)。
- 其中路由的挂载比较特别,先推入一个路由中间件函数,在函数内通过
url和method
注册路由及回调,最后根据请求信息执行对应的回调。
- 通过
events
模块监听访问路径(router.get
),当请求进来时,执行middwares(异步串行compose
),并在末尾返回ctx.body
(语义化)。
- 在若干中间件函数中,需要挂载某些对象及获取某些全局的信息,因此需要全局的上下文作为媒介
(ctx)
。
- 且在请求与响应时需要对应的API及信息,均挂载在对应的对象上
request、response
。
核心源码实现(珠峰)
初始化&中间件&对象挂载
// 引入中间件版
const http = require("http");
const context = require("./utils/context");
const response = require("./utils/response");
const request = require("./utils/request");
// const composeForNext = require('./utils/middleware')
class Koa {
constructor() {
this.middlewares = [];
}
listen(...args) {
// 开启服务,传入参数
const server = http.createServer(async (req, res) => {
/**
* 为回调函数注入ctx上下文对象
* ctx解析
* 1. 整合了request、response
* 2. 通过setter、getter 优雅地获取url、method等值。
* 通过ctx.body的方式,封装res.end()返回数据,,使代码更为可读。
*/
const ctx = this.createContext(req, res);
console.log("middlewares", this.middlewares);
// 引入中间件,聚合函数,使其同步执行
let fns = this.composeForNext(this.middlewares);
await fns(ctx);
res.end(ctx.body);
});
server.listen(...args);
}
use(callback) {
console.log("callback", callback);
// 加入回调函数组
this.middlewares.push(callback);
}
createContext(req, res) {
// 创建ctx对象
const ctx = Object.create(context);
// 为ctx对象注入request,当访问ctx.req时,实际是访问request内的变量
ctx.request = Object.create(request);
// 为ctx对象注入response,当访问ctx.req时,实际是访问response内的变量
ctx.response = Object.create(response);
// 层层传递,将req的值赋予request再赋予ctx
// 通过getter、setter对部分关键值的读写进行覆盖
ctx.req = ctx.request.req = req;
ctx.res = ctx.response.res = res;
return ctx;
}
composeForNext(middlewares) {
// 返回一个将函数数组同步化的函数(回调地狱)
// 通过递归及promise,保证函数的执行顺序。
return function (ctx) {
// 执行递归函数
return dispatch(0);
function dispatch(i) {
// 根据索引获取函数体
let fn = middlewares[i];
// 如果无对应函数体,即最后一个next
if (!fn) {
// 返回空执行承诺,有可能上一个函数是异步函数,
// 即await的,所以要保证返回的值是promise形式的。
return Promise.resolve();
}
// 如果有函数体
return Promise.resolve(
// 函数体执行,并且将下一个函数传入该函数体
fn(ctx, function next() {
// 执行下一个函数体,并返回下一个函数的执行承诺。
// 如果不返回则该函数体不会被放置在promise里,则失去同步性。
return dispatch(i + 1);
})
);
}
};
}
}
module.exports = Koa;
中间件补充
- 中间件的本质是一个函数数组。在
Koa
中,会为其中的函数注入两个实参(ctx,next)
,分别表示上下文对象以及下一个要执行的中间件函数。
- 当有多个中间件的时候,本质上是一种嵌套调用(异步串行),就像洋葱图一样:

关于动态加载中间件
- 在某些应用场景中,开发者可能希望能够动态加载中间件,例如当路由接收到某个请求后再去加载对应的中间件,但在
Koa
中这是无法做到的。
- 原因其实已经在前面了,
Koa
应用唯一一次加载所有中间件是在调用listen
方法的时候,即使后面再调用app.use
方法,也不会生效了。
context处理(上下文挂载对象)
module.exports = {
get url(){
return this.request.url;
},
get body(){
return this.response.body;
},
set body(val){
this.response.body = val;
},
get method(){
return this.request.method;
}
}
module.exports = {
get url(){
return this.req.url;
},
get method(){
return this.req.method.toLowerCase()
}
}
module.exports = {
_body: "",
get body() {
return this._body;
},
set body(val) {
this._body = val;
},
};
Koa路由
- koa路由本身是即为了减少判断,使用的策略模式,本质就是注册对象规则,然后匹配
class Router {
constructor() {
// 栈队列
this.stack = [];
}
// 注册路由的具体API
register(path, methods, middleware) {
let route = { path, methods, middleware };
this.stack.push(route);
}
// 注册get请求路由
get(path, middleware) {
this.register(path, "get", middleware);
}
// 注册post请求路由
post(path, middleware) {
this.register(path, "post", middleware);
}
// 最后路由挂载到app上,即开始监听
route() {
let stock = this.stack;
// 最后的中间件
return async function (ctx, next) {
let currentPath = ctx.url;
let route;
// 通过循环规则匹配当前路由
for (let i = 0; i < stock.length; i++) {
let item = stock(i);
// 路径相同且请求方式一样
if (
currentPath === item.path &&
item.methods.indexOf(ctx.methods) >= 0
) {
// 赋值对应的回调函数
route = item.middleware;
break;
}
}
// 函数执行
if (typeof route === "function") {
route(ctx, next);
return;
}
};
}
}