本文将介绍拦截器设计模式和它在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 的方式,代码看起来更容易理解,也更健壮,所有的东西都在一个地方,你仍然可以对函数进行不同的处理,在一个地方测试逻辑。