【typescript工具库】用函数增强的方法实现compose编程

大家好,我是Electrolux。今天这个文章讲一讲我ts工具库的一个利器-函数增强

什么是函数增强

我也不知道这个词有没有,没有就当作造了一个词语出来吧哈哈。这玩意跟函数劫持和函数化编程的compose的概念差不多。可以用于修改和扩展现有的 JavaScript 函数。但是区别在于compose基本上是属于嵌套关系。由于嵌套关系,因此常常比较难以理解。

这个函数方法是我从我的工具库里面抽离出来的一个函数。区别于传统的compose,我这里引用了优先级这一个概念进行扁平化处理。这个方法允许你劫持一个函数,或者进行多个函数在同一平面的组合。添加额外的逻辑,然后返回一个经过增强的新函数

它通常包括以下几个要素:

  • 一个目标函数:这是你想要增强的函数。
  • 一个或多个增强函数:这些函数添加额外的逻辑或修改目标函数的行为。
  • 一个优先级概念:用于确定增强函数的执行顺序。

实现函数增强

我们将使用 TypeScript 来实现一个简单的函数增强器。这个函数增强器包含以下核心部分:

  • fnType 接口:用于定义函数类型,包括优先级和函数本身。
  • enhance 类:主要的增强器类,用于管理目标函数和增强函数。
  • addFunction 方法:用于添加增强函数和它们的优先级。
  • next 方法和 asyncNext 方法:用于执行函数增强器中的所有函数,根据优先级排序执行。

下面是一个简单的函数增强器示例:

typescript 复制代码
/**
 * @des 可以看作是函数增强 | 保存劫持函数 | 添加逻辑 | 返回enhance后的增强逻辑
 */
interface fnType {
    priority: number;
    fn: Function
}
class enhance {
    fnArray: Array<fnType>
    enhance: Record<any, any>
    /**
     * @des 被劫持的函数默认优先级是0
     */
    constructor(targetFn: Function) {
        this.fnArray = [{
            priority: 0,
            fn: targetFn
        }]
        /**
         * @des 函数共享的变量
         */
        this.enhance = {
​
        }
    }
    addFunction(enhanceFn: Function, priority: number) {
        this.fnArray?.push({
            fn: enhanceFn,
            priority
        })
    }
    // 同步
    next(arg: any) {
        this.fnArray = this.fnArray?.sort((a, b) => {
            return a.priority - b.priority
        })
        for (let i = 0; i < this.fnArray?.length; i++) {
            this.fnArray[i].fn.call(this, arg)
        }
    }
    // 异步
    async asyncNext(arg: any) {
        this.fnArray = this.fnArray?.sort((a, b) => {
            return a.priority - b.priority
        })
        for (let i = 0; i < this.fnArray?.length; i++) {
            let res = await this.fnArray[i].fn.call(this, arg)
            if(res == "stop"){
                break
            }
        }
    }
}

简单聊一下这些函数吧

在这个示例中,我们首先创建了一个异步函数 sleepA,然后创建了一个名为 hello 的增强函数。接着,我们创建了一个函数增强器 enhancer,将 console.log 作为目标函数,并使用 addFunction 方法将 hello 函数添加到增强器中。

最后,我们使用 asyncNext 方法执行增强器中的函数,并查看增强器的状态。

作为开发者和使用者,需要注意下面几点

  • 执行顺序: 增强函数的执行顺序由优先级决定,低优先级的函数将首先执行。
  • 上下文绑定 : 确保在增强函数中正确绑定 this 上下文,以访问增强器的状态。
  • 异步处理 : 如果增强函数包含异步操作,使用 asyncNext 来确保它们按顺序执行。

我们来一段helloworld 吧

函数埋点 | 增强普通函数

统计 某一个函数执行次数,这里以前我们都是使用闭包来统计,现在我们可以 取到 变量enhance 来获取几个函数共同的 变量。下面的示例中我用console.log作为 需要增强的普通函数并且统计他执行的次数

javascript 复制代码
async function hello(this: enhance) {
    this.enhance.count = this.enhance.count ??  0
    this.enhance.count=this.enhance.count+1
    console.log("执行次数:",this.enhance.count)
}
​
let ftest = new enhance(globalThis.console.log)
ftest.addFunction(hello, 1)
ftest.asyncNext(`\x1B[92m 我是第一次输出 \x1B[0m`)
ftest.asyncNext(`\x1B[92m 我是第二次输出 \x1B[0m`)
ftest.asyncNext(`\x1B[92m 我是第三次输出 \x1B[0m`).then(()=>{
    console.log("三次输出完成:",ftest.enhance)
})

输出结果如下

makefile 复制代码
我是第一次输出 
我是第二次输出
我是第三次输出
执行次数: 1
执行次数: 2
执行次数: 3
三次输出完成: { count: 3 }

可以看到我们的console.log 是比较优雅的被我们劫持到了

函数记忆化 | 增强 promise异步

这是我之前写的 函数记忆的工具方法,我们要将他改造成扁平化的函数增强。当然我认为这种闭包的方式足够精简,但是用函数记忆化似乎更方便各个函数更加明确的进行组合。下面是未改造前面的代码

typescript 复制代码
function memoizeAsync<T>(
  func: (...args: any[]) => Promise<T>
): (...args: any[]) => Promise<T> {
  const cache: Map<string, Promise<T>> = new Map();
​
  return async (...args: any[]): Promise<T> => {
    const key = JSON.stringify(args);
​
    if (cache.has(key)) {
      console.log("触发记忆")
      return cache.get(key)!;
    }
​
    const resultPromise = func(...args);
    cache.set(key, resultPromise);
​
    try {
      const result = await resultPromise;
      return result;
    } catch (error) {
      cache.delete(key);
      throw error;
    }
  };
}
​
  
async function sleep(time:number): Promise<any> {
  return new Promise((resolve) => {
      setTimeout(() => {
          resolve(time)
      }, time);
  })
}
 
let sleepTest = memoizeAsync(sleep)
console.log(sleepTest(1000))
console.log(sleepTest(5000))
console.log(sleepTest(5000))

那么我们来用函数增强实现我们常用的函数吧

typescript 复制代码
interface EnhancePromise extends enhance{
    enhance:{
        cache:Map<string, any>
        result:any
    }
}
​
// 假装是 api请求
async function api(this: EnhancePromise,time:number): Promise<any> {
    console.log("触发了api")
    return new Promise((resolve) => {
        setTimeout(() => {
            let res ={id:time}
            this.enhance.cache.set(JSON.stringify(time),res);
            resolve(res)
        }, time);
​
    })
}
​
// 缓存
async function cache(this: EnhancePromise,args:any) {
    this.enhance.cache = this.enhance.cache ?? new Map()
    if(this.enhance.cache.get(JSON.stringify(args))){
        this.enhance.result = this.enhance.cache.get(JSON.stringify(args))
        return "stop"
    }
}
​
// 主函数
async function main(param:any){
    await api.bind(this)(param)
}
​
// 开始函数增强
let ftest = new enhance(main) 
ftest.addFunction(cache, -1)
ftest.asyncNext(1000).then(()=>{
​
}).then(async ()=>{
    ftest.asyncNext(2000)
}).then(()=>{
    ftest.asyncNext(1000).then(()=>{
        console.log("缓存的数据:",ftest.enhance.result)
    })
})
​

可以看到我们一开始 用 main作为主函数 ,主函数的优先级为0,然后设置了一个cache的函数增强方法放在他前面,优先级给一个-1。

最终输出

css 复制代码
触发了api
触发了api
缓存的数据: { id: 1000 }

看出来,3次调用模拟api,其中有一次走了缓存,所以输出了两次触发了api

代码在:github.com/yilaikesi/u...

相关推荐
哒哒哒52852018 小时前
React createContext 跨组件共享数据实战指南
前端
怪可爱的地球人18 小时前
UnoCss最新配置攻略
前端
Carry34518 小时前
Nexus respository 搭建前端 npm 私服
前端·docker
满天星辰18 小时前
使用 onCleanup处理异步副作用
前端·vue.js
POLITE318 小时前
Leetcode 142.环形链表 II JavaScript (Day 10)
javascript·leetcode·链表
qq_2290580119 小时前
lable_studio前端页面逻辑
前端
黎明初时19 小时前
React基础框架搭建8-axios封装与未封装,实现 API 请求管理:react+router+redux+axios+Tailwind+webpack
javascript·react.js·webpack
harrain19 小时前
前端svg精微操作局部动态改变呈现工程网架状态程度可视播放效果
前端·svg·工程网架图
独自破碎E19 小时前
Spring Boot支持哪些嵌入Web容器?
前端·spring boot·后端
大猫会长19 小时前
tailwindcss中,自定义多个背景渐变色
前端·html