拦截它:如何为不同的功能实现不同的逻辑

608 阅读3分钟

本文将介绍拦截器设计模式和它在Ts.ED中的应用。

简而言之,该模式在函数执行之前或之后注入一些逻辑功能。

它与中间件具有相同的性质,但拦截器不是与请求管道一起工作,而是处理函数调用,并且可以用不同的参数重复执行函数。

让我们假设一下它可能有帮助的场景。我有一个服务,向一些黑盒子发送请求。而这个黑盒子并不那么友好,在很可能出现异常的情况下,只有一个HTTP状态码,没有任何解释。

在收到403状态代码的情况下,很可能需要尝试重新认证用户并重复向服务发出请求。所以基本的重试机制并不适合这种情况,因为可能有多个不同的端点。

它可能看起来像这样。

async createPermamentCell(shapeId: number): Promise {
    try {
      return await this.createCell(`shape-${shapeId}`, true);
    } catch (error: any) {
       // try auth again
       if (error.status === 403) {
          const userEmail = httpContext.get(REQUEST_USER);
          const token = await this.authenticate(userEmail);
          httpContext.set(CLIENT,
            new Client({
              token: token,
            })
          );
          return await this.createCell(`shape-${shapeId}`, true);
       }
    }
}

// ...As well for other functions same try catch

或者像这样。

async retryOn403(functionToCall: (...args: any[]) => any, ...args: any[]): Promise {
    try {
      await functionToCall(...args);
    } catch (error: any) {
       // try auth again
       if (error.status === 403) {
          const userEmail = httpContext.get(REQUEST_USER);
          const token = await this.authenticate(userEmail);
          httpContext.set(CLIENT,
            new Client({
              token: token,
            })
          );
          return await functionToCall(...args);
       }
    }
}

// The function will be wrapped 

但这些东西可能会失去控制,并开始过自己的生活。换句话说,这样的代码将更难维护。

部分代码的改变会导致其他部分代码的改变,或者反过来说,在一个地方的改变会重塑那些预计不会被触及的代码。

让我们通过拦截器的方法来实现同样的逻辑。而在这个例子中,我将使用Ts.ED框架。当然,其他框架也包含这种机制,如Nestjs或.Net,等等。

我需要创建一个新的类,实现接口InterceptorMethods ,并使用装饰器@Interceptor()

@Interceptor()
export class ReAuthInterceptor implements InterceptorMethods {
    constructor() {}

    async intercept(context: InterceptorContext, next: InterceptorNext) {
        try {
            // It's possible to do somthing before call
            const result = await next();
            // Or right after the call
            return result;
        } catch (error: any) {
            if (error.status === 403) {
                try {
                    const userEmail = httpContext.get(REQUEST_USER);
                    const token = await this.authenticate(userEmail);
                    httpContext.set(CLIENT,
                      new Client({
                        token: token,
                      })
                    );
                    return next();
                } catch (error: any) {
                    _log.warn('Failed re auth', error);
                }
                // In case of exception on re auth return original exception
                return next(error);
            }
            // All other cases immediately return error
            return next(error);
        }
    }
}

任何服务或特定函数都可以用新的拦截器@Intercept(ReAuthInterceptor) 来装饰。

@Intercept(ReAuthInterceptor)
export class CellService implements CellService {
    constructor(@Inject(ClientProvider) private clientProvider: ClientProvider) {}

    // ... Many fucntions 

}

    

在拦截器的context 参数中,你可以找到参数或options (第二个参数,你可以在装饰器中使用@Intercept(ReAuthInterceptor, ‘My opts’) 。它可以帮助你为不同的函数实现不同的逻辑。

在Ts.ED中,有一个特殊的功能,当你试图在整个类中使用拦截器,并使用async和sync函数时,例如:。

@Intercept(ReAuthInterceptor)
export class CellService implements CellService {
    constructor(@Inject(ClientProvider) private clientProvider: ClientProvider) {}

    async createCell() {
      // ...
    } 

    getCurrentCell(): string {
      // ...
      return `some str`;
    }

}

第二个函数getCurrentCell() 的实际结果将不是string ,而是这个类型,它将是Promise

但除此之外,通过Intercept 的方式,代码看起来更容易理解,也更健壮,所有的东西都在一个地方,你仍然可以对函数进行不同的处理,在一个地方测试逻辑。