如何干净地编写具有不同回调重载参数的 TypeScript 函数

How to Cleanly Write a TypeScript Function With Different Callback Overload Parameters

这是 this question

的扩展

鉴于此代码:

class Animal {
    a: string;
}

class Dog extends Animal {
    b: string;
}

class Foo<T>{}

function test<T,A extends Dog>(animal:A, func: (p: A) => T): T;
function test<T,A extends Animal>(animal:A, func: (p: A) => Foo<T>): Foo<T>;
function test<T,A extends Animal>(animal:A, func: (p: A) => T|Foo<T>): T|Foo<T> {
    return func(animal);
}

是否有更简洁的方式来编写不需要 A 类型参数的重载?或者也许是编写其中任何一个的清洁工?基本上,该函数有条件地使用给定的 animal 调用给定的 func。如果给定一只狗,则返回类型 T。如果给定其他动物,则返回类型 Foo<T>

更新 1

我无法让@jcalz 版本工作,但我花了一段时间才意识到它与承诺有关,但我不确定如何解决这个问题。以下是我的 "ugly but it works" 方法和@jalz 的 "it's nice but it's broke" 方法:

class Animal {
    a: string;
}

class Dog extends Animal {
    b: string;
}

class Foo<T>{ }

function test<T, A extends Dog>(animal: A, func: (p: A) => Promise<T>): Promise<T>;
function test<T, A extends Animal>(animal: A, func: (p: A) => Promise<Foo<T>>): Promise<Foo<T>>;
function test<T, A extends Animal>(animal: A, func: (p: A) => Promise<T> | Promise<Foo<T>>): Promise<T | Foo<T>> {
    return func(animal);
}

const foo: Promise<Foo<string>> = test(new Animal(), (a) => { return Promise.resolve(new Foo<string>()); });
const other: Promise<string> = test(new Dog(), (d) => { return Promise.resolve(d.b); });

type AnimalFunc<T> = {
    (dog: Dog): Promise<T>;
    (animal: Animal): Promise<Foo<T>>;
}

function test2<T>(dog: Dog, func: AnimalFunc<T>): Promise<T>;
function test2<T>(animal: Animal, func: AnimalFunc<T>): Promise<Foo<T>>;
function test2<T>(animal: Animal, func: AnimalFunc<T>): Promise<T | Foo<T>> {
    return func(animal);
}

const foo2: Promise<Foo<string>>  = test2(new Animal(),
    (a) => {
        return Promise.resolve(new Foo<string>());
    }); // Errors: TS2345   Argument of type '(a: any) => Promise<Foo<string>>' is not assignable to parameter of type 'AnimalFunc<string>'.
        // Type 'Promise<Foo<string>>' is not assignable to type 'Promise<string>'.
        // Type 'Foo<string>' is not assignable to type 'string'.TypeScript Virtual Projects    C: \_Dev\CRM\WebResources\webresources\new_\scripts\Payment.ts  498 Active

const other2: Promise<string>  = test2(new Dog(), (d) => { return Promise.resolve(d.b); });

明白了

the function conditionally calls the given func with the given animal. If given a dog, type T is returned. If given some other animal, a type Foo<T> is returned.

表示参数 func 接受所有 Animal 输入,但会 return 不同类型,具体取决于其输入是否为 Dog。这意味着我将声明 func 到以下重载类型:

type AnimalFunc<T> = {
    (dog: Dog): T;
    (animal: Animal): Foo<T>;
}

然后,函数 test 只是将它的 animal 输入传递给它的 func 输入,并且 return 返回它返回的任何类型。为了实现这一点,我会这样声明 test

function test<T>(dog: Dog, func: AnimalFunc<T>): T;
function test<T>(animal: Animal, func: AnimalFunc<T>): Foo<T>;
function test<T>(animal: Animal, func: AnimalFunc<T>): T | Foo<T> {
    return func(animal);
}

希望对您有所帮助。


更新 1

@daryl :

This works for the definition, but not call sites. If I pass in a dog as the first parameter, my function must accept a dog and return an T, else it must accept an animal and return a Foo, At the call sites, it complains the the function isn't returning the other type (T or Foo)

在不了解您的所有用例的情况下,我无法判断最佳定义是什么。如果你真的有一个 AnimalFunc<T> 类型的函数,它应该可以工作:

function func1(dog: Dog): string;    
function func1(animal: Animal): Foo<string>;
function func1(animal: Animal): string | Foo<string> {
  if (animal instanceof Dog) {
    return "woof";
  }
  return new Foo<string>();
};

var dog: Dog = new Dog();
var cat: Animal = new Animal();
var dogTest: string = test(dog, func1);
var catTest: Foo<string> = test(cat, func1);

如果您尝试传入不同类型的函数,请详细说明用例。谢谢。


更新 2

@daryl :

This works for the definition, but not call sites. If I pass in a dog as the first parameter, my function must accept a dog and return an T, else it must accept an animal and return a Foo, At the call sites, it complains the the function isn't returning the other type (T or Foo)

好吧,我认为这与 Promises 没有太大关系。看起来你想要 func 或者 取一个 Dog 和 return 一个 Promise<T>, 或者 取一个 Animal 和 return 取一个 Promise<Foo<T>>,但不一定两者都取。也就是说,特定的 func 可能只想要 Dog 而不会接受 Cat。我原来不是这么理解的。

对于这种情况,那我说你想做的是:

function test3<T>(dog: Dog, func: (dog: Dog) => Promise<T>): Promise<T>;
function test3<T>(animal: Animal, func: (animal: Animal) => Promise<Foo<T>>): Promise<Foo<T>>;
function test3<T, A extends Animal>(animal: A, func: (animal: A) => Promise<T> | Promise<Foo<T>>): Promise<T> | Promise<Foo<T>> {
  return func(animal);
}

请注意,test3(前两行)的声明是为了 调用者 的利益而输入的,而输入的是实现(第三行)为了 实施者 的利益。如果您只关心调用 test3 的人的类型安全,但在您的实现中足够安全,您不需要 TS 为您验证类型,那么您可以将其实现为:

function test3<T>(dog: Dog, func: (dog: Dog) => Promise<T>): Promise<T>;
function test3<T>(animal: Animal, func: (animal: Animal) => Promise<Foo<T>>): Promise<Foo<T>>;
function test3(animal: any, func: any): any {
  return func(animal); // fine, but even return animal(func) would be accepted here, to disastrous results at runtime
}

具有通用 A 的实现签名与我认为的一样具体。它接受 animal 的任何类型的动物 A,以及一个肯定会接受 animal 和 return 的函数 func Promise<T>Promise<Foo<T>>。这对于调用 func(animal) 是足够安全的,但是你仍然可以通过像

这样的实现来欺骗类型检查器
function test3<T, A extends Animal>(animal: A, func: (animal: A) => Promise<T> | Promise<Foo<T>>): Promise<T> | Promise<Foo<T>> {
  return Promise.resolve(new Foo<T>()); // no error!!
}

这会导致第一个重载声明出现问题,因为它从来没有 return Promise<T>

希望对您有所帮助。