Введение в асинхронное программирование в TypeScript
Современная веб-разработка немыслима без асинхронного программирования. Будь то запросы к API, работа с файловой системой или таймеры — все эти операции требуют неблокирующего выполнения кода. TypeScript, как надстройка над JavaScript, предоставляет мощные инструменты для работы с асинхронностью, добавляя строгую типизацию и улучшенную читаемость кода.
В этом руководстве мы подробно разберем все аспекты асинхронного программирования в TypeScript: от базовых Promise до продвинутых паттернов с async/await и параллельного выполнения задач.
1. Основы Promise в TypeScript
Что такое Promise?
Promise (обещание) — это объект, представляющий результат асинхронной операции. Он может находиться в одном из трех состояний:
- Pending (ожидание) — начальное состояние, операция еще не завершена.
- Fulfilled (выполнено) — операция завершена успешно.
- Rejected (отклонено) — операция завершилась с ошибкой.
Создание Promise с типизацией
TypeScript позволяет строго типизировать возвращаемые значения Promise. Рассмотрим пример:
// Функция, возвращающая Promise с типом данных
function fetchUserData(userId: number): Promise<User> {
return new Promise<User>((resolve, reject) => {
setTimeout(() => {
const user: User = {
id: userId,
name: 'Иван Иванов',
email: 'ivan@example.com'
};
if (userId > 0) {
resolve(user);
} else {
reject(new Error('Некорректный ID пользователя'));
}
}, 1000);
});
}
// Интерфейс для типизации данных
interface User {
id: number;
name: string;
email: string;
}
// Использование Promise
fetchUserData(1)
.then((user: User) => {
console.log(`Пользователь: ${user.name}`);
})
.catch((error: Error) => {
console.error(`Ошибка: ${error.message}`);
});
Цепочки Promise (Chaining)
Promise можно объединять в цепочки для последовательного выполнения асинхронных операций:
function getUserPosts(userId: number): Promise<string[]> {
return new Promise((resolve) => {
setTimeout(() => resolve(['Пост 1', 'Пост 2', 'Пост 3']), 500);
});
}
function getPostComments(postTitle: string): Promise<string[]> {
return new Promise((resolve) => {
setTimeout(() => resolve(['Комментарий 1', 'Комментарий 2']), 300);
});
}
// Цепочка Promise
fetchUserData(1)
.then((user) => {
console.log(`Пользователь найден: ${user.name}`);
return getUserPosts(user.id);
})
.then((posts) => {
console.log(`Постов найдено: ${posts.length}`);
return getPostComments(posts[0]);
})
.then((comments) => {
console.log(`Комментариев к первому посту: ${comments.length}`);
})
.catch((error) => {
console.error('Произошла ошибка:', error);
});
2. Async/Await — современный подход
Синтаксис async/await
Ключевые слова async и await делают асинхронный код похожим на синхронный, что значительно улучшает его читаемость. Функция, помеченная async, всегда возвращает Promise.
// Асинхронная функция с типизацией возвращаемого значения
async function loadUserProfile(userId: number): Promise<UserProfile> {
try {
const user = await fetchUserData(userId);
const posts = await getUserPosts(userId);
return {
user,
posts,
lastLogin: new Date()
};
} catch (error) {
console.error('Не удалось загрузить профиль:', error);
throw error; // Пробрасываем ошибку дальше
}
}
interface UserProfile {
user: User;
posts: string[];
lastLogin: Date;
}
// Использование async/await
async function displayProfile() {
try {
const profile = await loadUserProfile(1);
console.log(`Профиль пользователя ${profile.user.name}`);
console.log(`Постов: ${profile.posts.length}`);
console.log(`Последний вход: ${profile.lastLogin}`);
} catch (error) {
console.error('Ошибка отображения профиля:', error);
}
}
displayProfile();
Обработка ошибок в async/await
Для обработки ошибок в асинхронных функциях используется конструкция try/catch. TypeScript позволяет типизировать ошибки через пользовательские классы:
class ApiError extends Error {
constructor(
public statusCode: number,
message: string
) {
super(message);
this.name = 'ApiError';
}
}
class NetworkError extends Error {
constructor(message: string) {
super(message);
this.name = 'NetworkError';
}
}
async function fetchWithRetry(url: string, retries: number = 3): Promise<Response> {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new ApiError(
response.status,
`HTTP ошибка: ${response.status}`
);
}
return response;
} catch (error) {
if (error instanceof ApiError) {
throw error; // Не повторяем при HTTP ошибках
}
if (attempt === retries) {
throw new NetworkError(
`Не удалось выполнить запрос после ${retries} попыток`
);
}
console.log(`Попытка ${attempt} не удалась, повторяем...`);
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
throw new Error('Недостижимый код');
}
3. Параллельное выполнение асинхронных операций
Promise.all — ожидание всех Promise
Метод Promise.all позволяет запустить несколько асинхронных операций параллельно и дождаться завершения всех:
interface DashboardData {
user: User;
posts: string[];
notifications: number;
}
async function loadDashboard(userId: number): Promise<DashboardData> {
try {
// Параллельное выполнение независимых запросов
const [user, posts, notifications] = await Promise.all([
fetchUserData(userId),
getUserPosts(userId),
getNotificationCount(userId)
]);
return {
user,
posts,
notifications
};
} catch (error) {