Koa
Compose
简介
Koa中间件机制就是组合函数(Compose)的概念, 将⼀组需要顺序执行的函数复合为⼀个函数,外层函数的参数实际是内层函数的 返回值 。洋葱圈模型可以形象表示这种机制,这也是Koa的 核心 。
如下图,假设我们注册中间件的顺序是A,B,C,那么中间件的执行顺序是,ABCBA。
那么外层函数的参数是内层函数的返回值该怎么理解呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const app = new Koa(); app.use(async (ctx, next) => { console.log('A') await next(); const router = {}; router["/html"] = (ctx) => { ctx.type = "text/html;charset=utf-8"; ctx.body = `<b>my name is:${ctx.body[0].name}</b>`; }; const fun = router[ctx.url]; fun && fun(ctx); }); app.use(async (ctx, next) => { console.log('A') ctx.body = [ { name: ctx.url, }, ]; }); app.listen(3000, (res) => { console.log("listening"); });
|
上述代码中,在B中间件给ctx的body赋值为一个数组,在A组件中就能通过ctx参数拿到这个数组,进行一些操作。
原理
其实配合es7的async / await 可以很方便的实现Compose。当然,Compose只是Koa重要部分,还有一些细节也一并实现。
可以看到下面代码引入了三个包 context,request,response ,其实就是对http做了一些简单的封装。
context
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| module.exports = { get url() { return this.request.url; }, get method() { return this.request.method; }, get body() { return this.response.body; }, set body(val) { this.response.body = val; }, };
|
request
1 2 3 4 5 6 7 8
| module.exports = { get url() { return this.req.url; }, get method() { return this.req.method.toLowerCase(); }, };
|
response
1 2 3 4 5 6 7 8
| module.exports = { get body() { return this._body; }, set body(val) { this._body = val; }, };
|
代码
Compose的实现其实跟express差不多,只不过用Promise封装了一下,但是因为async,await的存在,让它和express形成了不同的执行顺序。
除此之外我们定义了一个ctx(Context)对象,对http的req,res进行了简单的封装然后进行传递。
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
| const http = require("http"); const context = require("./context"); const request = require("./request"); const response = require("./response"); class Application { constructor() { this.middlewares = []; } use(fn) { this.middlewares.push(fn); } createContext(req, res) { const ctx = Object.create(context); ctx.request = Object.create(request); ctx.response = Object.create(response); ctx.req = ctx.request.req = req; ctx.res = ctx.response.res = res; return ctx; } compose(middlewares) { return function (ctx) { return dispatch(0); function dispatch(idx) { const fn = middlewares[idx]; if (!fn) { return Promise.resolve(); } return Promise.resolve( fn(ctx, function next() { return dispatch(idx + 1); }) ); } }; } listen(...args) { const server = http.createServer(async (req, res) => { const ctx = this.createContext(req, res); const fn = this.compose(this.middlewares); await fn(ctx); res.end(ctx.body); }); server.listen(...args); } }
module.exports = Application;
|
路由机制
Koa单独将路由拆分出来,通过use注册路由中间件。
1 2 3
| const router = new Router();
app.use(router.routes());
|
Koa的路由相对于Express来说要简单很多,只是实现了基本的功能,因为它不需要支持多个handle的调用。
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
| class Router { constructor() { this.stack = []; } register(path, method, middleware) { let route = { path, method, middleware }; this.stack.push(route); } get(path, middleware) { this.register(path, "get", middleware); } post(path, middleware) { this.register(path, "post", middleware); } routes() { const stack = this.stack; return async function (ctx, next) { let currUrl = ctx.url; let route; for (let i = 0; i < stack.length; i++) { const item = stack[i]; if (item.path === currUrl && item.method === ctx.method) { route = item.middleware; break; } } if (typeof route === "function") { route(ctx, next); return; } await next(); }; } }
module.exports = Router;
|