开启AdminJS成长之旅!这是我参与「AdminJS · 12 月更文挑战」的第12天,点击查看活动详情
什么是中间件模式
通常来说,提到中间件都是用于不同程序之间,比如消息中间件(消息队列);中间件模式是一种软件架构模式,它可以将多个不同的应用程序或系统连接在一起,实现通信和数据交换。中间件通常是独立于应用程序,它充当应用程序和应用程序之间的桥梁,为应用程序提供统一的访问接口。
但是在前端提到的中间件模式,就不得不提express,koa,redux等流行库的洋葱模型了,如下图显示
比如下面这段来自koa的示例代码,这个中间件会在每一个请求调用前后记录时间,在最后输出日志,就像一个洋葱一样,一层层执行
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
看到这里是不是觉得和AOP[面向切面编程]很相似,关于AOP内容可以看一下我前面的文章Typescript装饰器-AOP
个人觉得前端的中间件也算是AOP的一种实现,都可以用于对业务逻辑进行解耦
如何实现中间件模式
之前写过一个类似koa中间件实现,测试率百分百,可以在前端的业务代码中使用
首先安装依赖
npm install @lujs/middleware
API使用如下
const m1: IMiddleware<string> = (ctx, next) => {
console.log(1);
next();
console.log(6);
};
const m2: IMiddleware<string> = (ctx, next) => {
console.log(2);
next();
console.log(5);
};
const m3: IMiddleware<string> = (ctx, next) => {
console.log(3);
next();
console.log(4);
};
const runner = new MiddlewareRunner<string>();
runner.use(m1);
runner.use(m2);
runner.use(m3);
runner.run('');
// log 1 2 3 4 5 6
IMiddleware的泛型表示context的类型,另外还支持promise的方式调用
const m1: IMiddleware<string> = async (ctx, next) => {
console.log('async', 1);
await next();
console.log('async', 6);
};
const m2: IMiddleware<string> = async (ctx, next) => {
console.log('async', 2);
await next();
console.log('async', 5);
};
const m3: IMiddleware<string> = async (ctx, next) => {
console.log('async', 3);
await next();
console.log('async', 4);
};
const runner = new MiddlewareRunner<string>();
runner.use(m1);
runner.use(m2);
runner.use(m3);
runner.run('').then(() => {
console.log('finally');
});
比如前文说到的那个例子,在say函数前后都打印日志
const log = () => {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
const oldFn = descriptor.value
descriptor.value = function (...args:any[]) {
console.log('在函数执行前');
const res = oldFn.apply(this, args);
console.log('在函数执行后', res);
return res;
}
};
}
class A { @log() say() { return 'Hello world' } }
使用中间件可以这样实现
class A { say() { return 'Hello world' } }
const m1: IMiddleware<string> = (ctx, next) => {
console.log('before');
next();
console.log('after');
};
const say: IMiddleware<string> = (ctx, next) => {
const world = new A().say()
console.log(world)
};
const runner = new MiddlewareRunner<string>();
runner.use(m1);
runner.use(say);
runner.run('');
实际源码非常简单
export type IMiddlewareNextFunction = () => Promise<any>;
export interface IMiddleware<CTX> {
(context: CTX, next: IMiddlewareNextFunction): any | Promise<any>;
}
export class MiddlewareRunner<CTX> {
middleware: IMiddleware<CTX>[] = [];
middlewareCurrent = 0;
use = (middleware: IMiddleware<CTX>) => {
this.middleware.push(middleware);
};
run = async (context: CTX) => {
let err: Error | null = null;
const next = async () => {
const middleware = this.middleware[this.middlewareCurrent];
this.middlewareCurrent += 1;
if (typeof middleware === 'function') {
try {
const p = middleware(context, next);
if (p instanceof Promise) {
await p;
}
} catch (e) {
await next();
err = e as Error;
}
}
};
await next();
if (err) {
throw err;
}
return context;
};
}
代码在链接,期待你的star,谢谢!
推荐阅读
在线等,后端悄悄改了接口文档被我抓住了怎么办?
和后端对线 | 前端如何保存base64字符串为文件
释放生产力 | Yapi,swagger2,swagger3生成请求代码
什么?在React中也可以使用vue响应式状态管理
clean-js | 在hooks的时代下,使用class管理你的状态
clean-js | 手把手教你写一个羊了个羊麻将版
有没有一种可能,你从来都没有真正理解async