如何编写支持同步/异步函数且签名清晰的TypeScript装饰器?
日志装饰器:同步/异步统一实现方案
我有一个日志装饰器,代码如下:
interface IOptions<FuncType extends (...args: any[]) => any> { onStart: string | ((...args: Parameters<FuncType>) => string); onFinish?: string | ((value: ReturnType<FuncType>) => string); } export function loggedFunction<FuncType extends (...args: any[]) => any>( options: IOptions<FuncType>, func: FuncType ) { const { onStart, onFinish } = options; function logged(...args: Parameters<FuncType>) { const startLog = typeof onStart === "string" ? onStart : onStart(...args); console.log(startLog); const result = func(...args); if (onFinish) { const finishLog = typeof onFinish === "string" ? onFinish : onFinish(result); console.log(finishLog); } return result; } return logged; }
这个装饰器需要和函数返回值交互,但不能修改原函数的签名。目前它在处理异步函数时会失效——因为异步函数返回Promise,onFinish会直接接收Promise对象而不是实际的resolved值。
我想到的两种解决方案都存在逻辑重复的问题:
- 写同步版和异步版两个函数,主体逻辑几乎一致,仅返回调用部分不同
- 使用函数表达式,根据情况赋值同步/异步Lambda,但还是要重复95%的逻辑
对于复杂装饰器来说,这种重复逻辑的方案并不友好。有没有办法只通过同步/异步判断分支,实现单一逻辑的装饰器?
统一实现方案
可以通过统一处理同步和异步结果的方式,避免逻辑重复,同时保证类型签名正确:
interface IOptions<FuncType extends (...args: any[]) => any> { onStart: string | ((...args: Parameters<FuncType>) => string); onFinish?: string | ((value: Awaited<ReturnType<FuncType>>) => string); } export function loggedFunction<FuncType extends (...args: any[]) => any>( options: IOptions<FuncType>, func: FuncType ): FuncType { const { onStart, onFinish } = options; const logged = (...args: Parameters<FuncType>) => { // 处理开始日志 const startLog = typeof onStart === "string" ? onStart : onStart(...args); console.log(startLog); const result = func(...args); // 通用结果处理逻辑 const handleResult = (value: Awaited<ReturnType<FuncType>>) => { if (onFinish) { const finishLog = typeof onFinish === "string" ? onFinish : onFinish(value); console.log(finishLog); } }; // 判断结果是否为Promise,分支处理 if (result instanceof Promise) { return result.then((resolvedValue) => { handleResult(resolvedValue); return resolvedValue; }) as ReturnType<FuncType>; } else { handleResult(result as Awaited<ReturnType<FuncType>>); return result; } }; return logged as FuncType; }
方案说明
- 类型适配:用
Awaited<ReturnType<FuncType>>自动处理同步/异步的返回值类型——同步函数直接取返回值类型,异步函数取Promise resolved后的实际类型。 - 逻辑复用:提取
handleResult函数统一处理结束日志,避免同步/异步分支中重复代码。 - 分支判断:通过
instanceof Promise区分同步和异步结果,异步结果用.then捕获resolved值后再处理日志。 - 签名保持:通过类型断言保证装饰器返回的函数与原函数签名完全一致,不会改变原函数的同步/异步特性。
测试示例
同步函数
const add = loggedFunction( { onStart: "开始计算加法...", onFinish: (result) => `计算完成:${result}` }, (a: number, b: number) => a + b ); add(2, 3); // 输出: // 开始计算加法... // 计算完成:5
异步函数
const fetchUser = loggedFunction( { onStart: "开始请求用户数据...", onFinish: (user) => `请求完成:${JSON.stringify(user)}` }, async () => { await new Promise(resolve => setTimeout(resolve, 1000)); return { id: 1, name: "张三" }; } ); fetchUser(); // 输出: // 开始请求用户数据... // 1秒后输出:请求完成:{"id":1,"name":"张三"}
内容的提问来源于stack exchange,提问作者Biller Builder
相关产品推荐
相关产品推荐

