likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 48

Env: Typescript5.2 + RxJs + Node16

Git: github.com/chelizichen…

File: github.com/chelizichen…

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

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

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

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

/*
 * @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哪个先执行。

如下面代码所示:

@Controller("validate")
class ValidateController {

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

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

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

现在你有两种选择

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

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

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

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

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

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

然后就开始快乐的重构了

先创建响应流

// 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方法。

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,之类的也会很好的处理。

const UsePipe = (tarsuPipe:TarsusPipe) =>{
    return function (value:any,context:ClassMethodDecoratorContext){
        _.set(context.metadata,"__rx__pipe__",tarsuPipe)
    }
}
转载自:https://juejin.cn/post/7302344328243298314
评论
请登录