Это отдельный ответ, потому что он представляет другой подход. Одна из проблем, с которой люди иногда сталкиваются с сигнатурами перегрузки, заключается в том, что они не всегда ведут себя интуитивно с объединениями параметров . Вот глупый пример:
// call signatures
function foo(x: string): number;
function foo(x: number): string;
// implementation
function foo(x: string | number): number | string {
return (typeof x === 'string') ? x.length : "a".repeat(x);
}
Функция foo()
принимает string
и возвращает number
, или принимает number
и возвращает string
. И это работает как ожидалось:
const num: number = foo("string"); // okay
const str: string = foo(12345); // okay
Но люди ожидают, что вы можете передать ему что-то типа string | number
и получить значение типа number | string
. Иногда это ожидание происходит из-за смешения сигнатуры реализации с сигнатурой вызова, в других случаях просто кажется, что компилятор должен иметь возможность выбрать несколько перегрузок и выполнить их объединение. Но этого не происходит. Компилятор выбирает только одну сигнатуру перегрузки (во всяком случае, по крайней мере, для TS3.3. Раньше было невозможно вызвать объединение типов функций, но теперь вы можете ... ну, с предостережения . Возможно, со временем произойдет объединение перегрузки):
const oops: string | number = foo(Math.random() < 0.5 ? "string" : 12345); // error!
В моем другом ответе предлагается исправить это, добавив подпись вызова, специально соответствующую объединению. И это работает:
function foo(x: string): number;
function foo(x: number): string;
function foo(x: string | number): number | string; // added
function foo(x: string | number): number | string {
return (typeof x === 'string') ? x.length : "a".repeat(x);
}
const num: number = foo("string"); // okay
const str: string = foo(12345); // okay
const oops: string | number = foo(Math.random() < 0.5 ? "string" : 12345); // okay
Но есть и другой способ. Вы можете использовать универсальные функции и условные типы , чтобы заменить три сигнатуры вызовов, например:
function foo<X extends string | number>(x: X): X extends string ? number : string;
function foo(x: string | number): number | string {
return (typeof x === 'string') ? x.length : "a".repeat(x);
}
const num: number = foo("string"); // okay
const str: string = foo(12345); // okay
const oops: string | number = foo(Math.random() < 0.5 ? "string" : 12345); // okay
Почему это работает?
Итак, обобщенный тип X
будет выводиться как любой подтип string | number
на основе переданного параметра. Для foo("string")
, X
выводится как строковый литерал type "string"
. Для foo(12345)
, X
выводится как числовой литерал тип 12345
. И в вызове с Math.random()
, X
выводится как "string" | 12345
. Таким образом, все вызовы должны быть успешными.
Что они возвращают? Вот где появляется условный тип. Тип X extends string ? number : string
означает, что если X
является подтипом string
, то условный тип будет number
. В противном случае условный тип будет string
. Таким образом, для foo("string")
, X extends string
имеет значение true, а тип возвращаемого значения - number
. Для foo(12345)
, X extends string
имеет значение false, а тип возвращаемого значения - string
. А что насчет этого типа объединения с Math.random()
? Ну, поскольку условные типы распределяются по объединениям , в итоге получается number | string
по желанию.
Вы можете или не хотите делать что-то похожее с вашей функцией:
type MaybeIterable<T> = AsyncIterable<T> | Iterable<T>;
type UnmaybeIterable<M extends MaybeIterable<any>> = M extends Iterable<infer T> ? Iterable<T> : M extends AsyncIterable<infer T> ? AsyncIterable<T> : never;
type CurriedBufferResult = {
<M extends MaybeIterable<any>>(curriedIterable: M): UnmaybeIterable<M>
};
export function buffer(
size: number
): CurriedBufferResult;
export function buffer<M extends MaybeIterable<any>>(size: number, iterable: M): UnmaybeIterable<M>;
export function buffer(size: number, iterable?: MaybeIterable<any>): CurriedBufferResult | UnmaybeIterable<any>
{
// impl here
return null!;
}
Это то, что вы хотите? Не уверен.