简体   繁体   中英

get function return type from generic type

I have a function that takes a class as an input, the return type of that function is determined by the class. Here what I got so far, I would expect res to be of type number . Is it even possible

abstract class Command<R> {}

abstract class CommandHandler<R, C extends Command<R>> {
  abstract execute(cmd: C): R
}

class GetNumberCommand extends Command<number> {}

class GetNumberCommandHandler extends CommandHandler<number, GetNumberCommand> {
  execute(cmd: GetNumberCommand) {
    return 1
  }
}

function mediator<C extends Command<R>, R>(cmd: C): R {
  // [ts] Type 'GetNumberCommandHandler' is not assignable to type 'CommandHandler<R, C>'.
  const handler: CommandHandler<R, C> = new GetNumberCommandHandler()

  return handler.execute(cmd)
}

// res should be of type number, it's `{}`
const res = mediator(new GetNumberCommand())

This line const handler: CommandHandler<R, C> = new GetNumberCommandHandler() is not really type safe, since the compiler can't guarantee R and C will be compatible with number and GetNumberCommand of GetNumberCommandHandler , after all R and C could be any types.

The fact that R is not inferred correctly we could fix in one of two ways

Using a conditional type to extract R and only have oner type parameter to mediator

abstract class Command<R> {
    _type: R // we need to use the type parameter otherwise typescript will just ignore it 
}

abstract class CommandHandler<R, C extends Command<R>> {
    abstract execute(cmd: C): R
}

class GetNumberCommand extends Command<number> {}

class GetNumberCommandHandler extends CommandHandler<number, GetNumberCommand> {
    execute(cmd: GetNumberCommand) {
        return 1
    }
}

function mediator<C extends Command<any>>(cmd: C): (C extends Command<infer R> ? R : never) {
    const handler: CommandHandler<any, C> = new GetNumberCommandHandler() // we use any so no type assertion is needed

    return handler.execute(cmd)
}

const res = mediator(new GetNumberCommand()) // number

The second option is to not have the C parameter, since we don't use the actual type of the command ever again cmd could be typed as Command<R> and R will be inferred correctly :

abstract class Command<R> {
    _type: R // we need to use the type parameter otherwise typescript will just ignore it 
}

abstract class CommandHandler<R, C extends Command<R>> {
    abstract execute(cmd: C): R
}

class GetNumberCommand extends Command<number> {}

class GetNumberCommandHandler extends CommandHandler<number, GetNumberCommand> {
    execute(cmd: GetNumberCommand) {
        return 1
    }
}

function mediator<R>(cmd: Command<R>): R {
    const handler: CommandHandler<R, Command<R>> = new GetNumberCommandHandler() as any // we need a type assertion to make this work (and a custom way to ensure this is actually valid)

    return handler.execute(cmd)
}

const res = mediator(new GetNumberCommand()) // number

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