记录使用RxJs优化我的装饰器代码

Env: Typescript5.2 + RxJs + Node16

Git: github.com/chelizichen...

File: github.com/chelizichen...

想象一下,@UsePipe该如何实现?

大多数TypeScript玩家可能都写过类似的,我简单讲下思路:

因为构建装饰器的参数中包含了该成员函数(新版和老版都保留了)。所以我们只需要根据拥有的装饰器依次堆加新的方法即可。说起来可能有些抽象,我写个简单的示例。

ts 复制代码
/*
 * @desc pipe 装饰器 
*/
function UsePipe(pipe){
    return function(method){
        const originMethod = method;
        method = async function(...args){
            try{
               await pipe(...args)
               await originMethod(...args)
            }catch(error){
                handleError(error)
            }
        }
        return method;
    }
}

这样就可以实现管道验证而看起来不侵入业务代码,同理,拦截器还有其他各种各样的AOP概念都可以这样写。

但是上面那样写有个问题,由于实际上我们修改的是原成员函数,所以也算侵入了业务代码,并且在开发人员随意编写装饰器的位置,我们没办法保证pipe和interceptor哪个先执行。

如下面代码所示:

ts 复制代码
@Controller("validate")
class ValidateController {

    @Post('/changeStatus')
    @UsePipe(new TestValidatePipe())
    @UseInterceptor(new TestInerceptor())
    async changeStatus(){
        return {
            code:1
        }
    }
}

如果装饰器是从上往下依次执行,那么必定是先执行管道再执行拦截器。但是开发的同学可不会这么想。

拦截器就是要比管道先执行啊,哪有身份都还没验证就直接进管道的

现在你有两种选择

  1. 告诉开发同学检查他的代码并修正,逻辑从上往下的话,代码也要从上往下不要乱掉。
  2. 重新构建自己写的的代码。

所幸这整个库都是我一个人练手玩玩的,所以我没有历史包袱的重构自己的代码。

接下来我将引入RxJS来重构我的代码。

我所要的效果是:写上去了就会执行,而且执行的先后顺序都是事先定义好的

于是我先定义了一个代表从前往后依次响应的数组。

ts 复制代码
const httphandleStream = [
    {key:"__rx__interceptor__",value:'handle.call'},
    {key:"__rx__pipe__",value:"handle.call"},
    {key:"__rx__router__",value:undefined}
];
const handleHttpObserve = from(httphandleStream);

然后就开始快乐的重构了

先创建响应流

ts 复制代码
// func 是原本的成员函数
// context.metadata跟以前metadata差不多东西
const stream = handleHttpObserve.pipe(
    concatMap(async (Obj) => {
        try{
            const {key,value} = Obj;
            if(key === "__rx__router__"){
                const data = await func(req,res)
                _.set(context.metadata,'__rx__resp__',data)
                return data;
            }else{
                const fn = (_.get(context.metadata,key) || EmptyFunction) as Function
                await _.invoke(fn,value,...[this,req,res]);
            }
        }catch(error){
            _.set(context.metadata,'__rx__resp__',error)
            return error
        }
    }),
    takeWhile(result => !(_.isError(result))), // 期间发生错误直接退出
    scan((accumulator, currentValue) => [...accumulator, currentValue], []), // 使用 scan 累积结果
    catchError(error => { // 抛出异常至 error
        return throwError(()=>{
            _.set(context.metadata,'__rx__resp__',error)
            return error
        });
    })
)

再执行

其实这个时候next已经不重要了,他就是个打印每个执行的AOP函数返回值的东西,后续拓展可以用他来输出日志。 当有异常时,会进入到error中,并且RxJS管道关闭。否则一直执行,直到完成complete方法。

ts 复制代码
stream.subscribe({
    next(v){
        console.log(v);
    },
    error(data){
        const RESP = data || _.get(context.metadata,'__rx__resp__')
        if((RESP instanceof TarsusError || RESP instanceof Error)){
            (RESP as TarsusError).iCode = (RESP as TarsusError).iCode || -19;
            (RESP as TarsusError).iMessage = (RESP as TarsusError).iMessage || 'uncaught error'
        }
        res.send(RESP)
    },
    complete(){
        const iDATA = RxDone();
        const iRESP = _.get(context.metadata,'__rx__resp__')
        res.send(Object.assign({},iDATA,iRESP))
    }
});

最后到@UsePipe装饰器上,我们只需要在meta中定义有这么一个Pipe管道就行了。

同理换成其他的,UseInterceptor,CheckStatus,CheckRole,之类的也会很好的处理。

ts 复制代码
const UsePipe = (tarsuPipe:TarsusPipe) =>{
    return function (value:any,context:ClassMethodDecoratorContext){
        _.set(context.metadata,"__rx__pipe__",tarsuPipe)
    }
}
相关推荐
云空6 分钟前
《Three.js 3D坦克对战【AI单机版】》
javascript·人工智能·3d·three.js
默_笙12 分钟前
🚲 手写一个"Claude Code":我用 LangChain + ReAct 循环,让 AI 自己读文件、解释代码
前端·javascript
等咸鱼的狸猫1 小时前
# JS 核心六大主题:闭包内存模型、原型链查找、事件流机制与 Promise / Event Loop 实现视角复盘
前端·javascript
gis开发之家2 小时前
vue3直击架构痛点 依赖倒置与接口隔离——打造坚如磐石的插件与高阶组件(HOC)模式
javascript·架构·typescript·前端框架·vue3
柒和远方2 小时前
LeetCode 139. 单词拆分 —— 从暴力回溯到 DP 完全背包
javascript·python·算法
先吃饱再说2 小时前
流式输出完全指南:让 AI 对话像打字机一样流畅
javascript·vue.js
এ慕ོ冬℘゜4 小时前
原生 JS 实现自定义单选日历组件(无 UI 库、纯手写、禁用过去日期)
开发语言·javascript·ui
mONESY4 小时前
Skill 技能与 MCP 协议的底层逻辑与协同真相
javascript
南川琼语5 小时前
HTTP——AJAX
前端·javascript·ajax
不简说5 小时前
JS 代码技巧 vol.2 — 20 个数组/对象/字符串/正则的实用技巧
前端·javascript·面试