Typescript无法识别预期的重载函数签名

问题描述

我正在努力解决打字稿重载问题。

我正在将googleapis库与typescript一起使用,并尝试获取所有tagmanager帐户记录。 由于如果响应正文包含nextPagetoken,则googleapis list函数需要分页,因此我想创建一个对所有分页并抓取所有记录的函数

我的想法是创建一个像这样的咖喱功能。它以list函数作为参数,并继续使用nextPagetoken调用list函数,直到nextPagetoken不包含在list函数返回的数据中。

// ex. 1)
const allAccounts = await paginateall(tagmanager.accounts.list)({
  // specify tagmanager.accounts.list parameter
});

// ex. 2)
const allContainers = await paginateall(tagmanager.accounts.containers.list)({
  // specify tagmanager.accounts.containers.list parameter
});

我创建了一个paginateall签名,如下所示,但看来打字稿无法解决适当的重载。

export const paginateall = <P1,P2,R>(
  list: (params?: P1,options?: P2) => Promise<R>,): ((arg1: P1,arg2: P2) => Promise<Array<R>>) => async (a,b) => {
    // some procedure...
  };

const fetchAllAccounts = paginateall(tagmanager.accounts.list)
                                     ^^^

=== ERROR ===
Argument of type '{ (params: Params$Resource$Accounts$List,options: StreamMethodoptions): GaxiosPromise<Readable>; (params?: Params$Resource$Accounts$List | undefined,options?: Methodoptions | undefined): GaxiosPromise<...>; (params: Params$Resource$Accounts$List,options: StreamMethodoptions | BodyResponseCallback<...>,callback: ...' is not assignable to parameter of type '(params?: BodyResponseCallback<Schema$ListAccountsResponse> | undefined,options?: unkNown) => Promise<unkNown>'.
  Types of parameters 'params' and 'params' are incompatible.
    Type 'BodyResponseCallback<Schema$ListAccountsResponse> | undefined' is not assignable to type 'Params$Resource$Accounts$List'.
      Type 'undefined' is not assignable to type 'Params$Resource$Accounts$List'.

googleapis list函数具有6个重载,如下所示。我希望paginateall选择第二个签名。

1. list(params: Params$Resource$Accounts$List,options: StreamMethodoptions): GaxiosPromise<Readable>;
2. list(params?: Params$Resource$Accounts$List,options?: Methodoptions): GaxiosPromise<Schema$ListAccountsResponse>;
3. list(params: Params$Resource$Accounts$List,options: StreamMethodoptions | BodyResponseCallback<Readable>,callback: BodyResponseCallback<Readable>): void;
4. list(params: Params$Resource$Accounts$List,options: Methodoptions | BodyResponseCallback<Schema$ListAccountsResponse>,callback: BodyResponseCallback<Schema$ListAccountsResponse>): void;
5. list(params: Params$Resource$Accounts$List,callback: BodyResponseCallback<Schema$ListAccountsResponse>): void;
6. list(callback: BodyResponseCallback<Schema$ListAccountsResponse>): void;

如果您能告诉我为什么会这样的话,我将不胜感激。

==== 更新 ====

我使用TypeScript Playground重现了我的问题中的错误。 (我不想让它变得更简单)

type Params = {
    value1: string;
    value2: string;
}

type Options1 = {
    option1Value: string;
};

type Options2 = {
    option2Value: string;
};

type Resonse1 = {
    response1Value: string;
}

type Response2 = {
    response2Value: string;
}

type Callback<T> = () => T

declare function func(params: Params,options: Options1): Promise<Resonse1>;
declare function func(params?: Params,options?: Options2): Promise<Response2>;
declare function func(params: Params,options: Options1 | Callback<Resonse1>,callback: Callback<Resonse1>): void;
declare function func(params: Params,options: Options2 | Callback<Response2>,callback: Callback<Response2>): void;
declare function func(params: Params,callback: Callback<Response2>): void;
declare function func(callback: Callback<Response2>): void;

const anotherFunc = async <P1,R>(
  fn: (params?: P1,): Promise<R> => {
    return fn();
}

const test = anotherFunc(func);

解决方法

除非您实际上直接使用输入调用重载函数,否则编译器将无法选择重载。如果将重载函数传递给另一个函数并尝试使用泛型类型推断,则编译器将不会按照您希望的方式执行重载解析。它只是选择一个过载,通常是第一个或最后一个。这是TypeScript的设计限制,有关更多信息,请参见microsoft/TypeScript#30369

这里的解决方法是让选择您要使用的签名。这是一种方法:

const f: (params?: Params,options?: Options2) => Promise<Response2> = func;
const test = anotherFunc(f);

在这里,我们将func分配给变量f,该变量的类型与一个函数相对应,该函数仅具有我们要推断的调用签名。允许此分配。由于f没有超载,因此呼叫anotherFunc(f)会按需工作。

Playground link to code