迁移Koa中间件模型至前端业务中

Koa 链式中间件原理

洋葱圈模型

常见中间件原理浅析

其核心关键点在于

  1. 中间件的之间的链式调用-实现方法则是在当前中间件中保存下一个中间件的引用
  2. 洋葱圈模型的实现依赖于 async ... await,这也决定了中间件的固定写法
async function commonMiddleware(ctx, next){
  try{
    // do something
    await next()
    // do something
  }
  .catch(err){
    // handle err
  }    
}

设计模式之职责链模式

从Koa中间件的链式调用基本可推断出这是职责链模式的一种应用。
在这里插入图片描述
职责链模式主要解决 - 职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

js设计模式:什么是职责链模式

哪些业务场景可以使用职责链模式

  1. axios 的拦截器机制,这是由 请求拦截器真正要发出的请求响应拦截器 组成的链条,请求和响应信息在每个节点中进行传递,每个节点之间互不干扰
  2. 存在上下游关系的多步骤的逻辑处理关系同样适用 基于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;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值