Infer в TypeScript: пример использования
FurryCat 😼Мой первый самостоятельный кейс использования ключевого слова infer. Не могу не поделиться :)
Есть условная функция fetchAndFormat, которая получает данные из api и форматирует их. У нее два аргумента: fetcherFn и formatterFn. Причем второй - необязательный, если его нет, нужно вернуть оригинальные неотформатированные данные.
type FetcherFn = () => Promise<any>;
type FormatterFn = (data: any) => any;
async function fetchAndFormat(fetcherFn: FetcherFn, formatterFn?: FormatterFn) {
const data = await fetcherFn();
if (typeof formatterFn === 'function') {
return formatterFn(data);
}
return data;
}
// использование
type FetchedType = {
id: number;
name: string;
};
type FormattedType = {
id: string;
text: string;
};
function fetchData(): Promise<FetchedType> {
return Promise.resolve({ id: 234, name: "Product name" });
}
function formatData(data: FetchedType): FormattedType {
return { id: `${data.id}`, text: data.name };
}
fetchAndFormat(fetchData, formatData).then(result => {});
fetchAndFormat(fetchData).then(result => {});
Задача состоит в том, чтобы определить тип переменной result.

С какими типами данных работают fetcherFn и formatterFn неизвестно и передавать при каждом вызове типы в виде дженериков совсем не хочется. Поэтому попробуем вывести их автоматически внутри fetchAndFormat.
Введем два обобщенных типа T и K для аргументов функции:
async function fetchAndFormat<T extends FetcherFn, K>(fetcherFn: T, formatterFn?: K) {
const data = await fetcherFn();
if (typeof formatterFn === 'function') {
return formatterFn(data);
}
return data;
}
Для T можно сразу уточнить тип - extends FetcherFn. Для K у меня так сделать не получилось, потому что аргумент необязательный и дальше возникли проблемы с определением типа :(
Теперь постараемся вывести нужные нам типы из полученных с помощью infer:
type UnpackFetcherResult<T> = T extends (...params: any[]) => Promise<infer K> ? K : any; type UnpackFormatterResult<T> = T extends (data: any) => infer K ? K : any; type UnpackResult<T, K> = K extends FormatterFn ? UnpackFormatterResult<K> : UnpackFetcherResult<T>;
Если тип функции соответствует тому, что мы ожидаем, мы сможем получить тип данных, которые эта функция возвращает. В ином случае будет просто any.
Осталось только добавить возвращаемый тип в сигнатуру fetchAndFormat:
async function fetchAndFormat<T extends FetcherFn, K>(fetcherFn: T, formatterFn?: K): UnpackResult<T,K> {
//...
}
Теперь тип для возвращаемого значения определяется правильно:

