Koa 链式中间件原理
其核心关键点在于
- 中间件的之间的链式调用-实现方法则是在当前中间件中保存下一个中间件的引用
- 洋葱圈模型的实现依赖于
async ... await
,这也决定了中间件的固定写法
async function commonMiddleware(ctx, next){
try{
// do something
await next()
// do something
}
.catch(err){
// handle err
}
}
设计模式之职责链模式
从Koa中间件的链式调用基本可推断出这是职责链模式的一种应用。
职责链模式主要解决 - 职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。
哪些业务场景可以使用职责链模式
- axios 的拦截器机制,这是由
请求拦截器
、真正要发出的请求
、响应拦截器
组成的链条,请求和响应信息在每个节点中进行传递,每个节点之间互不干扰 - 存在上下游关系的多步骤的逻辑处理关系同样适用 基于Koa中间件替代前端Promise链式调用
Koa中间件与前端业务的结合
先来实现链式调用的核心逻辑
export type TNext = () => Promise<any>;
export type TMiddleware<T> = (ctx: T, next: TNext) => Promise<any>;
// 组合中间件链式调用的函数
function compose<T>(middlewares: TMiddleware<T>[]) {
if (!Array.isArray(middlewares)) throw new Error('middlewares stack must be an array!');
for (const fn of middlewares) {
if (typeof fn !== 'function') throw new Error('Middleware must be composed of functions!');
}
return function (context: T, next?: TNext) {
// last called middleware
let index = -1;
return dispatch(0);
function dispatch(i: number): Promise<any> {
if (i <= index) return Promise.reject(new Error('next() called multiple times'));
index = i;
let fn: TMiddleware<T> | undefined = middlewares[i];
if (i === middlewares.length) fn = next;
if (!fn) return Promise.resolve();
try {
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err);
}
}
};
}
接着实现中间件的添加,触发执行的逻辑
/**
* 中间件链式调用,减少不同处理逻辑之间的耦合
*/
class Middlewares<T extends object> {
/**
* 存储添加的中间件
*/
private middlewares: TMiddleware<T>[] = [];
/** 用于执行中间件的手柄 */
dispatch: (context?: Partial<T>) => Promise<void> = async () => {
throw new Error('dispatch 应在 init 后执行');
};
/**
* 添加中间件处理函数
* @param fn {IMiddleware<T>} 中间件函数
* @returns
*/
use(middleware: TMiddleware<T>) {
if (typeof middleware !== 'function') throw new TypeError('middleware must be a function!');
this.middlewares.push(middleware);
return this;
}
/**
* 初始化中间件并返回执行中间件的手柄
* @param defaultCtx 各个中间件共享数据的默认值
* @returns 返回用于执行中间件的手柄
*/
init(defaultCtx?: Partial<T>) {
const self = this;
const composeFn = compose<T>(this.middlewares);
/** 用于执行中间件的手柄 */
const dispatch = (context?: Partial<T>) => {
const ctx = {...defaultCtx, ...context} as T;
return self.fire(ctx, composeFn);
}
this.dispatch = dispatch;
return this;
}
/** 依次执行中间件 */
private async fire(ctx: T, composeFn: (context: T, next?: TNext) => Promise<any>) {
try {
await composeFn(ctx);
// 各个中间件执行完毕后会进入到这里,这里会拿到最终的运行结果
} catch (error) {
// 中间件中间执行报错会进入catch
console.log('error', error);
}
}
}
export default Middlewares;
最后看一个实际运行的实例
interface IContext {
name: string;
age: number;
text: string;
}
/** 使用示例 */
const middlewares = new Middlewares<IContext>();
/** 添加中间件 */
middlewares.use(async (ctx, next) => {
ctx.age = 11;
ctx.name = 'test1';
console.log(1, ctx);
await next();
console.log(11, ctx);
});
/** 添加中间件 */
middlewares.use(async (ctx, next) => {
ctx.age = 12;
ctx.name = 'test2';
console.log(2, ctx);
await next();
console.log(22, ctx);
});
/** 添加中间件 */
middlewares.use(async (ctx, next) => {
ctx.age = 13;
ctx.name = 'test3';
console.log(3, ctx);
await next();
console.log(33, ctx);
});
/** 初始化中间件并返回用于执行中间件的手柄 */
middlewares.init({
name: '',
age: 10
});
/** 执行添加的中间件 */
middlewares.dispatch({
text: '123'
});
输出结果
[LOG]: 1, {
"name": "test1",
"age": 11,
"text": "123"
}
[LOG]: 2, {
"name": "test2",
"age": 12,
"text": "123"
}
[LOG]: 3, {
"name": "test3",
"age": 13,
"text": "123"
}
[LOG]: 33, {
"name": "test3",
"age": 13,
"text": "123"
}
[LOG]: 22, {
"name": "test3",
"age": 13,
"text": "123"
}
[LOG]: 11, {
"name": "test3",
"age": 13,
"text": "123"
}
看一下如何基于 react + mobx 的整合
/* eslint-disable prefer-promise-reject-errors */
import { observable, action } from 'mobx';
import Middlewares from '@common/js/middlewares';
/**
* 中间件共享数据接口
*/
interface IContext {
submitData: ISubmitIdCert; // 表单数据
rootStore: Store; // 根store
}
class FormStore {
/**
* 存储store根节点信息
*/
rootStore: Store;
/**
* 用于组织执行各个子流程
*/
middlewares = new Middlewares<IContext>();
constructor(rootStore: Store) {
this.rootStore = rootStore;
this.initMiddlewares();
}
/** 初始化中间件 */
initMiddlewares() {
this.middlewares.use(async (ctx, next) => {
// 校验表单
await next();
});
this.middlewares.use(async (ctx, next) => {
/** 浏览器检测中间件 */
await next();
});
/** 摄像头检测中间件 */
this.middlewares.use(async (ctx, next) => {
/** 摄像头检测中间件 */
await next();
});
// 需要先初始化,才能后续进入调用dispatch开始中间件执行逻辑
this.middlewares.init({
rootStore: this.rootStore
});
}
/**
* 表单提交
* @param data
*/
@action.bound
async handleSubmit(data: ISubmitIdCert) {
try {
this.middlewares.dispatch({
submitData: data
});
} catch (err) {}
}
}
export default FormStore;