前言
使用过koa的小伙伴们都应该对洋葱模型有所了解,koa的独特的中间件流程控制就是通过洋葱模型来实现的,那么洋葱模型是什么,又是如何实现的呢?
洋葱模型介绍
介绍
上图是洋葱模型比较经典的一个图,通过这个图可以看到,洋葱模型就像一个洋葱一个,是分成多层的,而一个请求进入的时候,会从外到内依次经过每一层,到最内侧之后又从内到外依次经过每一次,而我们就可以在这每一层上面做自己需要做的操作。
比如一个请求,在koa接收到请求的时候,首先需要鉴权,然后需要对请求参数解析等等,完成请求之后,需要处理异常,添加请求头等等操作,而这些操作就可以放到洋葱模型的每一层上面做处理。
示例代码
如下代码为koa的一段示例代码:
- const Koa = require('koa');
- const app = new Koa();
- app.use(async (ctx, next) => {
- console.log(1);
- await next();
- console.log(2);
- });
- app.use(async (ctx, next) => {
- console.log(3);
- await next();
- console.log(4);
- });
- app.use(async (ctx, next) => {
- console.log(5);
- await next();
- console.log(6);
- });
- app.listen(8000);
执行上面的代码,会发现输出的数字顺序为1,3,5,6,4,2,与上面介绍的洋葱模型的执行顺序是一致的。
洋葱模型实现
首先我们先分析一下上面的代码app.use传入了一个异步的函数,而且app.use可以被使用多次,而这些函数在请求进入的时候会依次被调用,这是不是与发布订阅者模式是一致的,那首先我们来实现一个app.use
实现一个app.use
- export interface Middleware {
- (...rest: any): Promise<any>;
- }
- export default class Onion {
- middlewares: Middleware[] = [];
- constructor(middlewares: Middleware[] = []) {
- this.middlewares = middlewares;
- }
- use(middleware: Middleware) {
- this.middlewares.push(middleware);
- }
- }
上面代码我们定义了一个Onion类,通过这个类我们就可以将订阅函数进行收集,如下代码所示
- const onion = new Onion()
- onion.use(async(params, next)=> {
- console.log(1)
- await next()
- console.log(2)
- })
发布者在收集完订阅函数后需要有触发的时机,这时候就需要再给Onion添加一个执行函数
完善Onion
有小伙伴想到发布订阅者的实现代码,可能就会想到这样做:
- export default class Onion {
- middlewares: Middleware[] = [];
- constructor(middlewares: Middleware[] = []) {
- this.middlewares = middlewares;
- }
- use(middleware: Middleware) {
- this.middlewares.push(middleware);
- }
- execute(params: any) {
- this.middlewares.forEach(fn => {
- fn(params)
- })
- }
- }
但是这样做的话,就无法满足洋葱模型的先入后出的顺序,那我们应该怎么做呢?
1.定义 compose函数
- function compose(middlewares: Array<Middleware>) {
- if (!Array.isArray(middlewares)) {
- throw new Error('中间件必须是数组');
- }
- for (let i = 0; i < middlewares.length; i++) {
- if (typeof middlewares[i] !== 'function') {
- throw new Error('中间件的每一项都必须是函数');
- }
- }
- return (params: any) => {
- let index = 0;
- function dispatch(fn: Middleware | undefined) {
- if (!fn) {
- return Promise.resolve();
- }
- const next = () => dispatch(middlewares[++index]);
- return Promise.resolve(fn(params, next));
- }
- return dispatch(middlewares[index]);
- };
- }
2.实现execute
- export default class Onion {
- middlewares: Middleware[] = [];
- constructor(middlewares: Middleware[] = []) {
- this.middlewares = middlewares;
- }
- use(middleware: Middleware) {
- this.middlewares.push(middleware);
- }
- execute(params: any) {
- const fn = compose(this.middlewares);
- return fn(params);
- }
- }
通过定义componse函数,可以将中间件函数依次按照顺序来执行。
- const onion = new Onion();
- onion.use(async (params: any, next: Middleware) => {
- console.log(1);
- await next();
- console.log(2);
- });
- onion.use(async (params: any, next: Middleware) => {
- console.log(3);
- await next();
- console.log(4);
- });
- onion.use(async (params: any, next: Middleware) => {
- console.log(5);
- await next();
- console.log(6);
- });
- onion.execute({});
这样我们就实现了一个简易版的洋葱模型。
本文转载自微信公众号「前端有的玩」,可以通过以下二维码关注。转载本文请联系前端有的玩公众号。