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

TypeScript函数装饰器反向类型推断失败输出为unknown如何修复

问题原因

你的类型推断失效核心是TypeScript泛型参数的默认推导顺序和装饰器执行顺序不匹配:

  1. 原有decorate重载的泛型参数列表中,和最终handler直接相关的Input/Output位置靠后,TS会优先推断前面的装饰器相关泛型,此时无有效输入源会被推断为unknown,导致后续类型链路断裂
  2. addToInput/addToOutput中未直接参与入参的泛型参数,无法被从左到右的默认推断逻辑识别,需要依赖反向推导链路

修复方案

仅需要调整decorate所有重载的泛型参数顺序,将和最右侧handler绑定的Input/Output放在泛型列表最前面,让TS优先从handler推断出这两个基础类型,再反向推导前面所有装饰器的类型:

type Merge<A, B> = Omit<A, keyof B> & B;

// Context是存储输入数据的对象
type Handler<I, O> = (
  context: I
) => Promise<O>;

// 包装Handler的类型
type Decorator<I, NI, NO, O> = (
  next: Handler<NI, NO>
) => Handler<I, O>;

// 调整泛型顺序:将Input/Output放在最前面,优先从最右的handler推断
function decorate<Input, Output>(
  handler: Handler<Input, Output>
): Handler<Input, Output>;
function decorate<Input, Output, A, Z>(
  mw1: Decorator<A, Input, Output, Z>,
  handler: Handler<Input, Output>
): Handler<A, Z>;
function decorate<Input, Output, A, B, Y, Z>(
  mw1: Decorator<A, B, Y, Z>,
  mw2: Decorator<B, Input, Output, Y>,
  handler: Handler<Input, Output>
): Handler<A, Z>;
function decorate<Input, Output, A, B, C, X, Y, Z>(
  mw1: Decorator<A, B, Y, Z>,
  mw2: Decorator<B, C, X, Y>,
  mw3: Decorator<C, Input, Output, X>,
  handler: Handler<Input, Output>
): Handler<A, Z>;
function decorate(
  ...handlers: Function[]
) {
  return handlers.reduceRight((acc, h) => acc ? h(acc) : h)
}

interface InputContext {
  x: number;
}

interface OutputContext {
  a: string;
}

// 可正常推断为Handler<InputContext, OutputContext>
const myHandler0 = decorate(
  async ({ x }: InputContext): Promise<OutputContext> => {
    return {
      a: `x = ${x}`
    };
  }
);

myHandler0({ x: 5 })
  .then(o => console.log(`myHandler0: output = ${JSON.stringify(o)}`))
  .catch(e => console.error(`myHandler0: `, e));

// 基础空装饰器,不做任何操作
const passthrough = <I, O>(): Decorator<I, I, O, O> =>
  next => context => next(context);

// 可正常推断为Handler<InputContext, OutputContext>
const myHandler1 = decorate(
  passthrough(),
  async ({ x }: InputContext): Promise<OutputContext> => {
    return {
      a: `x = ${x}`
    };
  }
);

myHandler1({ x: 5 })
  .then(o => console.log(`myHandler1: output = ${JSON.stringify(o)}`))
  .catch(e => console.error(`myHandler1: `, e));

// 可修改输入/输出类型的高阶装饰器
const addToInput = <I, F, O>(factory: (context: I) => F): Decorator<I, Merge<I, F>, O, O> =>
  next => context => next({ ...context, ...factory(context) });

const addToOutput = <I, O, F>(factory: (context: O) => F): Decorator<I, I, O, Merge<O, F>> =>
  next => context => next(context).then(c => ({ ...c, ...factory(c) }) as Merge<O, F>);

// 现在可正确推断为Handler<InputContext, { a: string, b: boolean }>
const myHandler2 = decorate(
  addToInput((_c: InputContext) => ({ y: 10 })),
  addToOutput(_r => ({ b: true })),
  async ({ x, y }): Promise<OutputContext> => {
    // x和y都可以正常推断!
    return {
      a: `x = ${x + y}`
    };
  }
);

myHandler2({ x: 5 })
  .then(o => console.log(`myHandler2: output = ${JSON.stringify(o)}`))
  .catch(e => console.error(`myHandler2: `, e));

// 现在可正确推断为Handler<InputContext, { a: string, b: boolean }>
const myHandler3 = decorate(
  addToInput((_c: InputContext) => ({ y: 10 })),
  passthrough(),
  addToOutput(_r => ({ b: true })),
  async ({ x, y }): Promise<OutputContext> => {
    // x和y都可以正常推断!
    return {
      a: `x = ${x + y}`
    };
  }
);

myHandler3({ x: 5 })
  .then(o => console.log(`myHandler3: output = ${JSON.stringify(o)}`))
  .catch(e => console.error(`myHandler3: `, e));

扩展说明

如果需要支持任意个数的装饰器,可以用TS 4.0+的元组递归推断代替固定参数重载,不需要手动写每个参数个数的重载版本。

内容的提问来源于stack exchange,提问作者spyke

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.23 20:24:03