TypeScript装饰器如何通过this传属性?retry次数生产环境undefined问题排查
问题根因
这个问题和Kubernetes多Pod部署完全无关,是你的代码存在逻辑漏洞、且本地和生产的运行环境上下文不一致导致的:
- 第一个核心问题:首次调用未初始化重试次数
你现有的装饰器逻辑里,首次执行被装饰函数是直接在try块内调用原方法,此时还没有给this.currAttempt赋值,只有首次调用报错进入重试逻辑后才会给this挂载属性。如果你的生产场景下存在首次调用就读取retryAttempt的逻辑,就会拿到undefined,而本地测试大概率只有重试场景才会读取该字段,所以没有触发问题。 - 第二个核心问题:普通函数的
this上下文在严格模式下为undefined
你给出的示例里@retry直接装饰了普通函数foo,而非类的成员方法。非严格模式下普通函数的this指向全局对象,你可以挂载currAttempt属性;但生产环境的Node.js/TS编译默认会开启严格模式,此时普通函数的this为undefined,自然读取不到挂载的属性。 - 第三个逻辑漏洞:重试逻辑不会循环执行
你写的retrySyncMethod函数里,for循环执行一次就直接return result,根本不会走下一次循环重试,相当于配置的重试次数完全不生效。
修复方案
建议放弃用this传递重试次数的实现,避免上下文依赖问题,且完全不影响原有函数的调用参数:你可以在调用原函数时偷偷将重试次数追加到参数列表末尾,原有代码的调用传参不需要做任何修改。
修正后的代码如下:
export function retry(attempts: number): (target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) => void { return function (target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>): TypedPropertyDescriptor<any> { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]): any { // 从第一次调用就开始计数 let lastError: Error; for (let attempt = 1; attempt <= attempts; attempt++) { try { // 将重试次数追加到参数末尾,不影响原有传参 return originalMethod.apply(this, [...args, attempt]); } catch (err) { lastError = err as Error; } } // 所有重试都失败后抛出最终错误 throw lastError; }; return descriptor; }; } // 被装饰的函数只需要在参数末尾加上重试次数参数即可,原有调用完全不用改 @retry(3) function foo(host: string, path: string, optionaParam?: null, retryAttempt?: number) { if (retryAttempt > 2) { host = "changing host" } console.log("Mock Executing http request and throwing an error to simulate the need of retry"); throw Error("Got 500 - throwing error in order to retry"); }
如果你的被装饰函数是类成员方法,不需要修改参数的话,也可以用类实例存储重试次数,只需要在首次调用前初始化this.currAttempt = 1即可。
内容的提问来源于stack exchange,提问作者Raz Ronen
相关产品推荐
相关产品推荐

