Как правильно отклонить (reject) промис при использовании async/await в TypeScript?
Правильный способ: используйте throw
Да, вы абсолютно правы — в функциях async стандартным и правильным способом отклонить промис является оператор throw. Это не "коряво", а наоборот — это идиоматичный подход, который делает код более читаемым и предсказуемым.
Когда вы используете throw внутри async функции, JavaScript автоматически оборачивает выброшенное значение в отклонённый промис (rejected promise). Это работает точно так же, как вызов reject() в конструкторе new Promise().
Вот как должен выглядеть ваш исправленный код:
async function fetchUserData(userId: string): Promise<UserData> {
if (!userId) {
throw new Error('userId не может быть пустым');
}
const response = await fetch(`/api/users/${userId}`);
return response.json();
}
// Использование
try {
const data = await fetchUserData('');
} catch (error) {
console.error('Ошибка:', error.message); // "userId не может быть пустым"
}
Типизация ошибок в TypeScript
Чтобы сделать код ещё более надёжным, вы можете создать собственный класс ошибки:
class ValidationError extends Error {
constructor(message: string) {
super(message);
this.name = 'ValidationError';
}
}
async function fetchUserData(userId: string): Promise<UserData> {
if (!userId) {
throw new ValidationError('userId не может быть пустым');
}
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP ошибка: ${response.status}`);
}
return response.json();
}
Важные моменты:
- throw внутри async функции — это единственный способ отклонить промис, кроме возврата другого отклонённого промиса
- return Promise.reject() тоже работает, но это менее идиоматично:
async function fetchUserData(userId: string): Promise<UserData> {
if (!userId) {
return Promise.reject(new Error('userId не может быть пустым'));
}
// ... остальной код
}
- Всегда используйте try/catch при вызове async функций, чтобы обработать возможные ошибки
- TypeScript не может статически проверить, какие именно ошибки могут быть выброшены, поэтому хорошей практикой является создание пользовательских классов ошибок
Сравнение с классическим Promise
Для наглядности, вот как выглядит тот же код без async/await:
function fetchUserData(userId: string): Promise<UserData> {
return new Promise((resolve, reject) => {
if (!userId) {
reject(new Error('userId не может быть пустым'));
return;
}
fetch(`/api/users/${userId}`)
.then(response => response.json())
.then(resolve)
.catch(reject);
});
}
Как видите, синтаксис async/await значительно упрощает код, а throw заменяет reject() естественным образом.
И последнее: не забывайте, что если вы не обработаете ошибку через try/catch, она "всплывёт" до глобального обработчика, что может привести к неожиданным падениям приложения. Всегда обрабатывайте ошибки!
Альтернативный подход: возврат объекта Result
В некоторых проектах (особенно в функциональном стиле) предпочитают не использовать исключения для управления потоком, а возвращать специальный объект, содержащий либо успешный результат, либо ошибку. Это называется паттерном Result или Either.
type Result<T> =
| { success: true; data: T }
| { success: false; error: string };
async function fetchUserData(userId: string): Promise<Result<UserData>> {
if (!userId) {
return { success: false, error: 'userId не может быть пустым' };
}
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
return { success: true, data };
} catch (error) {
return { success: false, error: 'Ошибка загрузки данных' };
}
}
// Использование
const result = await fetchUserData('123');
if (result.success) {
console.log(result.data);
} else {
console.error(result.error);
}
Преимущества:
- Явная обработка ошибок — TypeScript заставляет проверять поле
success - Нет неожиданных исключений
- Удобно для композиции функций
Недостатки:
- Больше шаблонного кода
- Не все библиотеки поддерживают такой подход
- Теряется возможность использовать
try/catchдля перехвата любых ошибок
Выбирайте подход в зависимости от стиля вашего проекта. Для большинства случаев стандартный throw — это оптимальное решение.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги
Где и как эффективно учить Python: Лучшие курсы и интерактивные платформы для самостоятельного обучения