简体   繁体   中英

Typescript: Change function argument type

I'm designing an Hystrix-like reliability API and expect it to work like this:

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(...);

Now I have problem to define the typing to make it return a new function, whose first argument type can become an array of the original type.

It doesn't work:

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();
//    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Got compile error:

error TS2345: Argument of type '(url: string) => Promise' is not assignable to parameter of type 'TargetFn<{}>'.

  • Types of parameters 'url' and 'args_0' are incompatible.

    • Type '{}' is not assignable to type 'string'.

The compiler sometimes has issues inferring type parameters where they are related.

An approach that will work and actually is simpler in my opinion is to use conditional types to extract the first parameter type instead:

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

Note a better version of WrappedFn would actually return a function with multiple overloads instead of one that take has the first argument A1|A1[] . Each overload would return R or R[] as appropriate:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM