Типы для callback функций
Типизация callback функций в TypeScript — описание сигнатуры функции-аргумента: типов её параметров и возвращаемого значения, что позволяет компилятору проверять корректность callback при передаче и вызове.
Зачем нужно
Callbacks — функции, передаваемые как аргументы другим функциям. Без типизации callback компилятор не знает что передать и что ожидать в ответ. Типизированные callback делают API самодокументируемым и ловят ошибки несоответствия сигнатур.
Где используется
- Асинхронные функции с callback-паттерном (Node.js стиль)
- forEach, map, filter, reduce — типы callback выводятся из массива
- Обработчики событий
- Dependency injection (передача стратегий, обработчиков)
- Promise executor:
new Promise((resolve, reject) => ...)
Основной контент
Базовый синтаксис
// Тип callback в параметре функции
function doSomething(callback: => void): void {
callback;
}
// С параметрами
function transform(value: string, fn: (s: string) => string): string {
return fn(value);
}
transform("hello", (s) => s.toUpperCase()); // TypeScript выводит тип s: string
Именованные типы callback
// type alias для переиспользования
type Predicate<T> = (value: T) => boolean;
type Transform<A, B> = (input: A) => B;
type Comparator<T> = (a: T, b: T) => number;
type Consumer<T> = (value: T) => void;
function filter<T>(arr: T, predicate: Predicate<T>): T {
return arr.filter(predicate);
}
function sort<T>(arr: T, compare: Comparator<T>): T {
return [...arr].sort(compare);
}
const isEven: Predicate<number> = (n) => n % 2 === 0;
filter([1, 2, 3, 4], isEven); // [2, 4]
Callback с опциональными параметрами
// Callback может иметь меньше параметров чем ожидается — это нормально
type ClickHandler = (event: MouseEvent, index: number) => void;
// Передаём callback только с одним параметром
const handler: ClickHandler = (event) => {
console.log(event.type); // OK — index можно игнорировать
};
// Array методы используют этот принцип
[1, 2, 3].forEach((item) => console.log(item)); // не нужен index и array
[1, 2, 3].forEach((item, index) => console.log(item, index));
Node.js callback стиль (errback)
type NodeCallback<T> = (error: Error | null, result?: T) => void;
type AsyncCallback<T> = (err: Error | null, data: T) => void;
function readConfig(path: string, callback: NodeCallback<string>): void {
fs.readFile(path, "utf8", (err, data) => {
if (err) {
callback(err, undefined);
} else {
callback(null, data);
}
});
}
readConfig("./config.json", (err, data) => {
if (err) {
console.error(err.message);
return;
}
console.log(data); // data: string | undefined
});
Async callback и Promise
// Callback возвращающий Promise
type AsyncPredicate<T> = (value: T) => Promise<boolean>;
type AsyncTransform<A, B> = (input: A) => Promise<B>;
async function filterAsync<T>(
arr: T,
predicate: AsyncPredicate<T>
): Promise<T> {
const results = await Promise.all(arr.map(predicate));
return arr.filter((_, i) => results[i]);
}
const isAdult: AsyncPredicate<User> = async (user) => {
const data = await fetchAge(user.id);
return data.age >= 18;
};
Callback с this-контекстом
// Если callback использует this — нужна явная аннотация
interface EventTarget {
on(event: string, callback: (this: EventTarget, data: unknown) => void): void;
}
class Button {
label: string;
constructor(label: string) {
this.label = label;
}
click(handler: (this: Button) => void): void {
handler.call(this);
}
}
Частые ошибки
- Ожидать что callback получит строже типизированный
this— без явной аннотацииthisв callback будетany. - Передать callback с большим количеством параметров чем ожидается — TypeScript разрешает меньше параметров, но не больше.
- Путать
=> voidи=> undefined— в callbackvoidозначает «возвращаемое значение игнорируется»; можно вернуть что угодно. - Не типизировать callback явно при сложных HOF — без явных типов TypeScript может не вывести параметры callback.
Связанные темы
- Типизация функций -- параметры и возврат
- Типизация Event Handlers
- Generic функции
- Типы -- null, undefined, void, never
- _MOC TypeScript