【typescript工具库】带你在生产环境封装职责链模式

前言

我发现网上有很多人都在说职责链模式,但是却没有人将其抽象出来,或者说抽象的程度并不能达到足以上生产环境的程度。刚好项目中有需要用到,正好做一个业务场景的复盘

项目背景

先简单说一下用到这设计模式的需求的背景,也算是我们这个项目的复盘吧,我们当时需要做一个平台,类似于文件管理和模型管理。然后我们需要判断上传文件类型.其中有xml,普通文件(video,img),model,图纸(dwg格式),然后进行不同的操作。在这期间需要跟bimface 和 阿里oss 以及我们研究院的后端进行交互。由于业务隐私问题,这里我用的是几个版本前上传xml的解析图(部分)

我们有一个请求次数 = num(task)。而这个task 的数量有足足1000以上。也就是说有1000+的请求在这个上传过程中需要被处理的。这一部分的处理如果大家感兴趣的话以后会讲。我举这一个例子是为了说明说在这个链路中,只要有一个流程或者一个promise请求失败了,那么就整体失败了。并且这个xml图还只是上传与xml逻辑中的一环。

我们看到单单xml上传这里一系列交互过程,涉及到的请求已经非常多了。假如我们用常规逻辑进行判断,不难想象到后期维护起来有多酸爽。那么我们怎么构建整个链路的处理逻辑呢。职责链就可以有用武之地了。不妨我们写一个工具类

职责链模式

简介

职责链模式是一种行为设计模式,它允许你将请求沿着处理链传递,每个处理器都有机会处理请求或将其传递给下一个处理器。

实现职责链模式

数据结构

我们要实现一个职责链,我们首先应该想到的是,我们的代码的数据结构需要怎么进行定义。我们可以发现我们职责链的逻辑是沿着处理链传递的,学过算法的朋友应该反应的过来,这其实是一个链表。

链表的最简示例如下

kotlin 复制代码
  class ListNode {
      val: number
      next: ListNode | null
      constructor(val?: number, next?: ListNode | null) {
          this.val = val
          this.next = next
      }
  }

好的,接下来就是如何组织代码了,这里我们用 ChainData 代替 val(通过dataSet进行设置),然后用 asyncNext代替我们上面的next方法.这是数据结构的问题。

节点的值

我们接着聊一下数据的传递吧,我们在链表中单个链表中各个节点所存储的数据不一样。这里我们也是但是区别在于我们这个需要应用于业务,因此我们的数据为了持久化,各个节点存储的数据应该是增量的。

节点跳转

我们在一个事件处理完后,同步的代码我们可以通过return 值直接监听到,异步的逻辑我们在完成后却不能直接监听到,因此我们在使用的时候需要手动指定回调。也就是asyncNext方法。我们在定义class中也需要简单判断一下是不是同步方法。看看是否执行继续下一个处理器

错误事件 | 自定义事件

这一部分其实基于我们的发布者订阅者模式,关于事件,除了错误事件,还有完成事件和进度条事件之类的可以加上去。这一部分也会暴露给使用者

最后使用 TypeScript 来实现一个职责链模式吧。这个模式包含以下核心部分:

  • Chain 类:用于创建职责链。
  • ChainData :每一个链表中的数据。
  • emit 方法:用于触发事件。
  • dataSet 方法:用于初始化数据。
  • asyncNext 方法:用于执行下一个处理器。
  • nodeSet 方法:用于设置下一个处理器。
  • passRequest 方法:用于执行当前处理器并决定是否继续下一个处理器。

职责链代码

kotlin 复制代码
type OrderResult = 'chainNext' | any;
​
type OrderHandler = (...args: any[]) => OrderResult;
type emitNameType = 'finish' | 'error';
type ChainDataType = {
  eventBus?:{
    finish:Array<Function>
    error:Array<Function>
  }
  [key:string]:any
}
/**
 * @des 链式调用数据
 */
class Chain {
  private fn: OrderHandler;
  private nodeNext: Chain | null;
  // 一般会被当作初始数据
  ChainData: ChainDataType;
  constructor(fn: OrderHandler) {
    this.fn = fn;
    this.ChainData = {};
    this.nodeNext = null;
  }
​
  /**
   * @des 触发某一个事件
   * @param name
   * @param data 给function的值
   */
  emit = (name: emitNameType, data: any) => {
    if(this.ChainData.eventBus){
      if (this.ChainData.eventBus[name]) {
        this.ChainData.eventBus[name].forEach((element: Function) => {
          element(data);
        });
      } else {
        throw new Error('没有这个事件');
      }
    }
  };
​
  /**
   * @des 初始化数据
   * @param data
   */
  dataSet(data: ChainDataType): void {
    this.ChainData = data;
  }
  /**
   * @des settimeout的 异步
   * this.fn(...args) 不会返回值那么就要放到后面去 | 避免 return 判断。因为不会return 值
   * @param args
   * @returns
   */
  asycNext(): OrderResult {
    if (this.nodeNext) {
      this.nodeNext.dataSet(this.ChainData);
      return this.nodeNext.passRequest();
    }
    return this.fn();
  }
​
  nodeSet(nodeNext: Chain): void {
    this.nodeNext = nodeNext;
  }
​
  passRequest(): OrderResult {
    // 执行这个方法
    const res = this.fn();
    if (res === 'chainNext') {
      if (this.nodeNext) {
        // 所有的函数都要用 chain 方法包起来,否则没有 this.nodeNext
        this.nodeNext.dataSet(this.ChainData);
        return this.nodeNext.passRequest();
      }
    }
    if (this.nodeNext) {
      this.nodeNext.dataSet(this.ChainData);
    }
​
    return res;
  }
}

使用示例

  • 第一步:校验数据
  • 第二步:业务逻辑
  • 第三步:结束事件
typescript 复制代码
export interface ExtendedChainData extends Chain{
  ChainData:{
    init:{
      xml_id:number,
      model_id:number
    }
    res:any
  }
}
​
/**
 * @des 异步数据
 * @returns 
 */
const sleep = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({
        code: 200,
        data:[
          {id:1,name:"小明"}
        ]
      });
    }, 1000);
  });
};
​
/**
 * @des 初始化数据
 * @param this 
 * @returns 
 */
function initPost(this:ExtendedChainData) {
  console.log('数据校验:');
  if(!this.ChainData.init){
    this.emit("error","使用者触发error事件")
    return
  }
  return 'chainNext';
}
​
​
/**
 * @des 异步逻辑
 * @param this 
 */
async function asyncPost(this:ExtendedChainData) {
  let data = await sleep() 
  this.ChainData.res = data
  this.asycNext()
}
​
​
/**
 * @des 异步逻辑
 * @param this 
 */
 async function lastPost(this:ExtendedChainData) {
  this.emit("finish","使用者触发finish事件")
}
​
​
​
// 1. 初始化 链条
const chainInitPost = new Chain(initPost);
const chainAsyncPost = new Chain(asyncPost);
const chainLastPost = new Chain(lastPost);
// 2. 设置关系 注意异步 用await 处理逻辑后调用 asyncNext 方法
chainInitPost.nodeSet(chainAsyncPost);
chainAsyncPost.nodeSet(chainLastPost);
// 3. 开始执行。从一开始的链条开始执行
chainInitPost.dataSet({
  init:{
    xml_id:"1111111111",
    model_id:"22222222"
  },
  eventBus:{
    finish:[
      (e:any)=>{
        console.log(e)
        console.log(chainInitPost.ChainData.res)
      }
    ],
    error:[
      (e:any)=>{
        console.log(e)  
      }
    ]
  }
})
chainInitPost.passRequest();

输出

css 复制代码
数据校验:
使用者触发finish事件
{ code: 200, data: [ { id: 1, name: '小明' } ] }

代码链接:github.com/yilaikesi/u...

相关推荐
恋猫de小郭4 分钟前
Flutter 状态管理基准测评,一个很有趣的观点
android·前端·flutter
雪芽蓝域zzs9 分钟前
前端编辑组件wangEditor
前端
风骏时光牛马37 分钟前
稳定性治理及疑难问题深度剖析与实战复盘
前端
JeffongTan9 小时前
在LWC中镶嵌VF Page获取用户IP
前端·javascript·salesforce
不cong明的亚子11 小时前
web live多屏互动平台
javascript·经验分享·音视频
陆枫Larry11 小时前
Astro 是什么
前端
是立不是利13 小时前
写给设计师的 CSS 博客美学指南
前端·css
dsyyyyy110113 小时前
JS复习,难点,易错点总结
开发语言·javascript·ecmascript
默_笙13 小时前
🏝 Docker 就是"房地产开发":从施工图纸到小区物业的容器化指南
前端·javascript
计算机魔术师13 小时前
Rohan Paul 谈用时间跨度衡量智能体能力,并引用 OpenAI 自动化研究实习生里程碑
前端