Урок 08

Обработка ошибок в асинхронном коде

try/catch с await

async function load() {
    try {
        const data = await fetchData();
        return data;
    } catch (error) {
        console.error('Ошибка:', error);
        throw error;
    }
}

catch в промисах

fetchData()
    .then((data) => process(data))
    .catch((error) => console.error(error));

Один catch на всю цепочку.

Ошибка = unknown

В строгом TypeScript catch даёт unknown:

try {
    await fetchData();
} catch (error: unknown) {
    if (error instanceof Error) {
        console.error(error.message);
    } else {
        console.error('Неизвестная ошибка');
    }
}

В JavaScript — просто error.

Типы ошибок

// Ошибка сети
fetch('/api').catch((err) => {
    if (err instanceof TypeError) {
        console.log('Сеть недоступна');
    }
});

// HTTP-ошибка (проверяем вручную)
const res = await fetch('/api');
if (!res.ok) {
    throw new Error(`HTTP ${res.status}`);
}

// Отмена
if (err.name === 'AbortError') {
    console.log('Запрос отменён');
}

Не глотай ошибки

Плохо:

try {
    await save();
} catch (e) {
    // тихо
}

Хорошо:

try {
    await save();
} catch (e) {
    console.error('Не удалось сохранить:', e);
    showError('Ошибка сохранения');
    throw e; // или обработать
}

finally

async function load() {
    showSpinner();
    try {
        return await fetchData();
    } finally {
        hideSpinner();
    }
}

finally выполнится даже при ошибке.

Множественные операции

const results = await Promise.allSettled([
    saveUser(user),
    saveProfile(profile),
    saveSettings(settings),
]);

const errors = results.filter((r) => r.status === 'rejected');
if (errors.length) {
    console.error('Ошибки:', errors.map((e) => e.reason));
}

allSettled не отклоняется — можно обработать каждую ошибку.

Ошибка в одном из Promise.all

try {
    const [a, b, c] = await Promise.all([p1, p2, p3]);
} catch (error) {
    // упадёт при первой ошибке
    // остальные промисы продолжат выполняться
    console.error(error);
}

Если нужно знать все ошибки — allSettled.

Unhandled rejection

// Плохо
fetchData(); // никто не ловит ошибку

// В Node.js — предупреждение и падение в новых версиях
process.on('unhandledRejection', (reason) => {
    console.error('Unhandled:', reason);
});

// В браузере
window.addEventListener('unhandledrejection', (event) => {
    console.error(event.reason);
});

Всегда добавляй catch или try/catch.

Кастомные ошибки

class ApiError extends Error {
    constructor(message, status) {
        super(message);
        this.name = 'ApiError';
        this.status = status;
    }
}

async function request(url) {
    const res = await fetch(url);
    if (!res.ok) {
        throw new ApiError(`HTTP ${res.status}`, res.status);
    }
    return res.json();
}

try {
    await request('/api');
} catch (error) {
    if (error instanceof ApiError && error.status === 404) {
        // обработать 404
    }
}

Повторные попытки

async function retry(fn, attempts = 3, delay = 1000) {
    for (let i = 0; i < attempts; i++) {
        try {
            return await fn();
        } catch (error) {
            if (i === attempts - 1) throw error;
            await new Promise((r) => setTimeout(r, delay));
        }
    }
}

const data = await retry(() => fetchData(), 3, 1000);

С экспоненциальной задержкой:

async function retryBackoff(fn, attempts = 3, baseDelay = 1000) {
    for (let i = 0; i < attempts; i++) {
        try {
            return await fn();
        } catch (error) {
            if (i === attempts - 1) throw error;
            const delay = baseDelay * 2 ** i;
            await new Promise((r) => setTimeout(r, delay));
        }
    }
}

Логирование

async function withLogging(fn) {
    try {
        return await fn();
    } catch (error) {
        console.error('[Error]', {
            message: error.message,
            stack: error.stack,
            timestamp: new Date().toISOString(),
        });
        throw error;
    }
}

Итоги

  • try/catch с await
  • .catch для промисов
  • Проверяй res.ok в fetch
  • Не глотай ошибки
  • allSettled для всех результатов
  • Кастомные ошибки для типов
  • Retry с backoff для сети