记录使用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)
    }
}
相关推荐
秋雨凉人心4 小时前
简单发布一个npm包
前端·javascript·webpack·npm·node.js
哥谭居民00016 小时前
将一个组件的propName属性与父组件中的variable变量进行双向绑定的vue3(组件传值)
javascript·vue.js·typescript·npm·node.js·css3
踢足球的,程序猿6 小时前
Android native+html5的混合开发
javascript
前端没钱6 小时前
探索 ES6 基础:开启 JavaScript 新篇章
前端·javascript·es6
一条不想当淡水鱼的咸鱼8 小时前
taro中实现带有途径点的路径规划
javascript·react.js·taro
土豆炒马铃薯。8 小时前
【Vue】前端使用node.js对数据库直接进行CRUD操作
前端·javascript·vue.js·node.js·html5
温轻舟9 小时前
前端开发 -- 自动回复机器人【附完整源码】
前端·javascript·css·机器人·html·交互·温轻舟
赵大仁9 小时前
深入解析 Vue 3 的核心原理
前端·javascript·vue.js·react.js·ecmascript
张小虎在学习9 小时前
JS 数组创建、访问、常用方法
javascript
张小虎在学习9 小时前
JS 三种添加元素的方式、区别( write、createElement、innerHTML )
javascript