首页 > 解决方案 > 打字稿:更改函数参数类型

问题描述

我正在设计一个类似 Hystrix 的可靠性 API,并希望它能够像这样工作:

const target = request-promise;
const dispatcher = new Dispatcher(target)
  .fallback(...)
  .circuitBreaker()
  .throttle()
  .end();

// The new function has same signature as the target, 
// except that first argument becomes an array.
// It finally calls:
// request-promise('http://a.com') 
// or 
// request-promise('http://b.com')
dispatcher(['http://a.com', 'http://b.com'])
  .then(...);

现在我有问题定义类型以使其返回一个新函数,其第一个参数类型可以成为原始类型的数组。

它不起作用:

type TargetFn<T> = (...args: [T, ...any[]]) => Promise<any>;
type WrappedFn<F> = F extends TargetFn<infer T> ? TargetFn<T | T[]> : unknown;

class Dispatcher<F extends TargetFn> {

  constructor(private targetFn: F) {}

  end(): WrappedFn<T> {
    // ...
  }      
}

function chain<T, F extends TargetFn<T>>(fn: F): Dispatcher<T, F> {
  return new Dispatcher<T, F>(fn);
}

chain((url: string) => Promise.resolve(url)).end();
//    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

得到编译错误:

错误 TS2345:“(url:字符串)=> Promise”类型的参数不可分配给“TargetFn<{}>”类型的参数。

  • 参数“url”和“args_0”的类型不兼容。

    • 类型“{}”不可分配给类型“字符串”。

标签: typescript

解决方案


编译器有时会在推断它们相关的类型参数时遇到问题。

在我看来,一种可行且实际上更简单的方法是使用条件类型来提取第一个参数类型:

type TargetFn = (...args: any[]) => Promise<any>;
type WrappedFn<F> = F extends (a: infer A1, ...args: infer U) => Promise<infer R> ? (a: A1|A1[], ...args: U) => Promise<R> : unknown;

class Dispatcher<F extends TargetFn> {

constructor(private targetFn: F) {}

    end(): WrappedFn<F> {
        return null as any;
    }      
}

function chain<F extends TargetFn>(fn: F): Dispatcher<F> {
    return new Dispatcher<F>(fn);
}

const o = chain((url: string) => Promise.resolve(url)).end(); 
o("") // call it with a string
o(["", ""]) // or an array

请注意,更好的版本WrappedFn实际上会返回一个具有多个重载的函数,而不是带有第一个参数的函数A1|A1[]。每个重载都会返回RR[]酌情返回:

type WrappedFn<F> = F extends (a: infer A1, ...args: infer U) => Promise<infer R> ? {
    (a: A1, ...args: U): Promise<R> 
    (a: A1[], ...args: U): Promise<R[]> 
}: unknown;


const o = chain((url: string) => Promise.resolve(url)).end(); 
o("") // call it with a string, returns Promise<string>
o(["", ""]) // or an array, returns Promise<string[]>

推荐阅读