简体   繁体   English

具有由输入参数指定的返回类型的函数的 TypeScript 类型

[英]TypeScript type for a function with a return type specified by an input parameter

I'm trying to specify a TypeScript type for a function that takes an input and an optional outputType parameter which determines the return type.我正在尝试为一个函数指定一个 TypeScript 类型,该函数接受一个input和一个确定返回类型的可选outputType参数。 If no outputType is specified the return type matches typeof input .如果未指定outputType ,则返回类型与typeof input匹配。 The return value may also be a Promise of that type.返回值也可能是该类型的Promise

For example,例如,

type Decrypter = (options: {
  input: string | Buffer | stream.Readable;
  outputType?: "string" | "buffer" | "stream" | undefined;
}) => X | Promise<X>;

Where X would be string , Buffer , or stream.Readable depending on the value of outputType , or the same type as options["input"] if outputType is undefined .其中X将是stringBufferstream.Readable ,具体取决于outputType的值,或者如果outputTypeundefined ,则与options["input"]的类型相同。

I feel like there is something that can be done with generics, extends , and possibly infer , but I can't quite get the right incantation!我觉得可以用泛型、 extends和可能的infer来做一些事情,但我不能完全得到正确的咒语!

Any ideas?有任何想法吗?

Below is an example of a basic implementation:下面是一个基本实现的例子:

const decrypter: Decrypter = async ({ input, outputType }) => {
  let encrypted: string;
  if (typeof input === "string") {
    encrypted = input;
    outputType ??= "string";
  } else if (Buffer.isBuffer(input)) {
    encrypted = input.toString("utf8");
    outputType ??= "buffer";
  } else if (Readable.isReadable(input)) {
    const buffers = [];
    for await (const buffer of input as Readable) {
      buffers.push(buffer);
    }
    encrypted = Buffer.concat(buffers).toString("utf8");
    outputType ??= "stream";
  } else {
    throw new Error("Invalid input");
  }

  const decrypted = encrypted.split("").reverse().join("");

  switch (outputType) {
    case "string":
      return decrypted;
    case "buffer":
      return Buffer.from(decrypted);
    case "stream":
      return Readable.from(decrypted);
    default:
      throw new Error("Invalid outputType");
  }
};

I'm using Node.js v18.4.0, TypeScript v4.7.4.我正在使用 Node.js v18.4.0、TypeScript v4.7.4。

The first thing we need is a map which maps from the string literal type to the return type.我们需要的第一件事是从字符串文字类型映射到返回类型的映射。

type TypeMap = {
  string: string
  buffer: Buffer
  stream: stream.Readable
}

For both input and output we add the generic types I and O to capture the type of the object passed to the function.对于inputoutput ,我们添加通用类型IO来捕获传递给函数的对象的类型。 We also constrain both generic types to their respective allowed inputs.我们还将这两种泛型类型限制为各自允许的输入。 We add never as the default type for O if a type for outputType was not provided.如果未提供outputType的类型,我们将never添加为O的默认类型。

type Decrypter = <
  I extends TypeMap[keyof TypeMap], 
  O extends keyof TypeMap = never
>(options: {
  input: I;
  outputType?: O;
}) => DecryptorReturn<O, I>;

For the return type we check if any type was provided for O .对于返回类型,我们检查是否为O提供了任何类型。 If yes, we can use the TypeMap to lookup the type.如果是,我们可以使用TypeMap来查找类型。

type DecryptorReturn<O extends keyof TypeMap, I> = [O] extends [never]
  ? I | Promise<I> 
  : TypeMap[O] | Promise<TypeMap[O]>

Playground 操场


Now some tests to see if it's working:现在进行一些测试以查看它是否有效:

const fn: Decrypter = {} as any

const res1 = fn({ input: "abc" })
//    ^? const res1: "abc" | Promise<"abc">

const res2 = fn({ input: "abc", outputType: "buffer" })
//    ^? const res2: Buffer | Promise<Buffer>

Looks good to me.在我看来很好。


When you writing functions with complex generic types, TypeScript will always have problems understanding the logic of your function implementation.当您编写具有复杂泛型类型的函数时,TypeScript 在理解函数实现的逻辑时总是会遇到问题。 For example, you try to assign a string to outputType .例如,您尝试将string分配给outputType But since outputType is of type O TypeScript will net let you assign anything to it.但是由于outputTypeO类型,TypeScript 将允许你为它分配任何东西。

So have some short-comings when trying to type the function itself.所以在尝试键入函数本身时会有一些缺点。 First of all, I would give input and outputType explicit types which are not generic so that you can assign things to them.首先,我会给出非通用的inputoutputType显式类型,以便您可以为它们分配东西。

(async ({ input, outputType }: {input: TypeMap[keyof TypeMap], outputType: keyof TypeMap}) => { ... }

TypeScript will now have problems with assigning this function to the type Decrypter because it is not generic. TypeScript 现在在将此函数分配给类型Decrypter时会遇到问题,因为它不是通用的。 I would use a simple type assertion to silence this error.我会使用一个简单的类型断言来消除这个错误。

const decrypter: Decrypter = (async ({ input, outputType }: {input: TypeMap[keyof TypeMap], outputType: keyof TypeMap}) => {
  /* ... */
}) as Decrypter

Playground 操场

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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