Обработка ошибок сети

Mithril — это лёгкий и производительный фреймворк для построения SPA на JavaScript, который включает собственный модуль для работы с HTTP-запросами через m.request. Корректная обработка сетевых ошибок является важной частью любой надёжной веб-приложения, так как позволяет реагировать на непредвиденные ситуации и поддерживать устойчивость интерфейса.

Основы m.request

m.request предоставляет обёртку над стандартным fetch, но с более удобным синтаксисом и встроенной поддержкой промисов. Его базовый вызов выглядит так:

m.request({
    method: "GET",
    url: "/api/data",
})
.then(response => {
    console.log(response);
})
.catch(error => {
    console.error(error);
});

Ключевые моменты:

  • method — HTTP-метод (GET, POST, PUT, DELETE).
  • url — адрес запроса.
  • data — объект данных для отправки на сервер.
  • headers — заголовки запроса.

Обработка ошибок HTTP

m.request автоматически отклоняет промис при сетевых ошибках, таких как невозможность соединения. Однако HTTP-статусы вроде 404 или 500 не всегда вызывают исключение, если не настроено явное преобразование. Для контроля таких ситуаций используется опция unwrapSuccess и обработка в .then:

m.request({
    method: "GET",
    url: "/api/data",
    unwrapSuccess: false
})
.then(response => {
    if (response.status >= 400) {
        throw new Error(`Ошибка сервера: ${response.status}`);
    }
    return response.data;
})
.catch(error => {
    console.error("Произошла ошибка запроса:", error.message);
});
  • unwrapSuccess: false заставляет m.request возвращать весь объект ответа, включая статус.
  • Проверка response.status позволяет самостоятельно реагировать на ошибки.

Использование try/catch с async/await

Для более современного синтаксиса и улучшенной читаемости кода применяют async/await:

async function fetchData() {
    try {
        const response = await m.request({
            method: "GET",
            url: "/api/data",
        });
        console.log(response);
    } catch (error) {
        console.error("Ошибка сети или сервера:", error);
    }
}

Особенности:

  • Исключения из m.request перехватываются блоком catch.
  • Можно добавлять логирование и уведомления пользователя.
  • Преимущество async/await — линейный поток кода, более удобный для сложных цепочек запросов.

Перехват специфических ошибок

В некоторых случаях требуется различать типы ошибок: сетевые проблемы, ошибки сервера и ошибки данных. Для этого m.request предоставляет объект ошибки с полями code, status и message:

m.request({
    method: "GET",
    url: "/api/data",
})
.catch(err => {
    if (err.code === 0) {
        console.error("Сетевая ошибка: сервер недоступен");
    } else if (err.status >= 500) {
        console.error("Ошибка сервера:", err.message);
    } else {
        console.error("Неизвестная ошибка:", err);
    }
});
  • err.code === 0 указывает на отсутствие соединения или таймаут.
  • err.status содержит HTTP-статус ответа сервера.
  • err.message описывает проблему.

Таймауты и повторные запросы

Mithril позволяет добавлять таймауты к запросам через timeout:

m.request({
    method: "GET",
    url: "/api/data",
    timeout: 5000
})
.catch(err => {
    console.error("Запрос не выполнен за 5 секунд:", err);
});

Для критичных данных часто реализуют механизм повторных попыток:

async function fetchWithRetry(url, retries = 3) {
    for (let i = 0; i < retries; i++) {
        try {
            const response = await m.request({ method: "GET", url });
            return response;
        } catch (error) {
            if (i === retries - 1) throw error;
        }
    }
}
  • Каждая ошибка инициирует повтор запроса.
  • После последней неудачной попытки ошибка выбрасывается наружу.

Централизованная обработка ошибок

В крупных приложениях удобно создавать отдельный модуль для работы с HTTP, где реализованы:

  • логирование ошибок;
  • уведомления пользователя;
  • повторные запросы;
  • фильтрация по типам ошибок.

Пример структуры:

export async function apiRequest(options) {
    try {
        return await m.request(options);
    } catch (err) {
        handleError(err);
        throw err;
    }
}

function handleError(err) {
    if (err.code === 0) alert("Нет соединения с сервером");
    else if (err.status >= 500) console.error("Серверная ошибка:", err.message);
    else console.warn("Ошибка запроса:", err);
}

Такой подход упрощает поддержку приложения и снижает дублирование кода.

Обработка ошибок при параллельных запросах

При множественных одновременных запросах полезно использовать Promise.allSettled для получения информации о каждом результате:

const requests = [
    m.request({ method: "GET", url: "/api/user" }),
    m.request({ method: "GET", url: "/api/orders" })
];

Promise.allSettled(requests)
    .then(results => {
        results.forEach(result => {
            if (result.status === "fulfilled") {
                console.log("Успешно:", result.value);
            } else {
                console.error("Ошибка запроса:", result.reason);
            }
        });
    });
  • fulfilled — успешный ответ.
  • rejected — ошибка запроса.
  • Позволяет обрабатывать каждый запрос отдельно, не прерывая цепочку.

Вывод

Обработка ошибок сети в Mithril требует понимания механизма промисов и особенностей m.request. Использование catch, async/await, таймаутов, повторных попыток и централизованных обработчиков обеспечивает устойчивость приложения и корректное взаимодействие с пользователем даже при сбоях соединения или ошибках сервера.