记录使用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)
    }
}
相关推荐
知识分享小能手20 小时前
微信小程序入门学习教程,从入门到精通,电影之家小程序项目知识点详解 (17)
前端·javascript·学习·微信小程序·小程序·前端框架·vue
Dever20 小时前
记一次 CORS 深水坑:开启 withCredentials 后Response headers 只剩 content-type
前端·javascript
Hilaku20 小时前
为什么我开始减少逛技术社区,而是去读非技术的书?
前端·javascript·面试
猪哥帅过吴彦祖20 小时前
第 8 篇:更广阔的世界 - 加载 3D 模型
前端·javascript·webgl
Asort21 小时前
JavaScript设计模式(十二)——代理模式 (Proxy)
前端·javascript·设计模式
tangdou36909865521 小时前
可怕!我的Nodejs系统因为日志打印了Error 对象就崩溃了😱 Node.js System Crashed Because of Logging
前端·javascript·后端
Mintopia21 小时前
🎨 数据增强技术在 AIGC 训练中的应用:提升 Web 生成的多样性
前端·javascript·aigc
xiaohua0708day21 小时前
关于解决js中MediaRecorder录制的webm视频没有进度条的问题
javascript·音视频
程序铺子21 小时前
如何使用 npm 安装 sqlite3 和 canvas 这些包
javascript·npm·node.js
通往曙光的路上21 小时前
day9_elementPlus2
javascript·vue.js·elementui