打字稿:更改函数参数类型

时间:2018-10-16 07:59:25

标签: typescript

我正在设计类似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:string)=> Promise'的参数不能分配给类型'TargetFn <{}>'的参数。

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

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

1 个答案:

答案 0 :(得分:1)

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

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

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[]>