You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写支持同步/异步函数且签名清晰的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;
}

方案说明

  1. 类型适配:用Awaited<ReturnType<FuncType>>自动处理同步/异步的返回值类型——同步函数直接取返回值类型,异步函数取Promise resolved后的实际类型。
  2. 逻辑复用:提取handleResult函数统一处理结束日志,避免同步/异步分支中重复代码。
  3. 分支判断:通过instanceof Promise区分同步和异步结果,异步结果用.then捕获resolved值后再处理日志。
  4. 签名保持:通过类型断言保证装饰器返回的函数与原函数签名完全一致,不会改变原函数的同步/异步特性。

测试示例

同步函数

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 13:30:51