Обработка ошибок, retry, отмена запросов (AbortController)
↑ 5.4 Работа с API: REST, GraphQL, WebSocket · ← Предыдущая · → Следующая
Зачем это на собесе
Гонки запросов и устойчивость: отмена устаревших запросов и разумные повторы.
Объяснение
Отмена
const ctrl = new AbortController();
fetch(url, { signal: ctrl.signal }); // axios: { signal: ctrl.signal }
ctrl.abort(); // AbortError
// Поиск: отменять предыдущий запрос при новом вводе
let current: AbortController | null = null;
async function search(q: string) {
current?.abort(); current = new AbortController();
try { results.value = await api.get("/search", { params: { q }, signal: current.signal }).then(r => r.data); }
catch (e) { if (axios.isCancel(e) || (e as Error).name === "AbortError") return; throw e; }
}
onUnmounted(() => current?.abort());
AbortSignal.timeout(5000); AbortSignal.any([userSignal, AbortSignal.timeout(5000)]);Vue Query автоматически передаёт signal в queryFn и отменяет при смене ключа/размонтировании.
Повторы (retry)
async function withRetry<T>(fn: (signal: AbortSignal) => Promise<T>, { attempts = 3, base = 300, signal }: { attempts?: number; base?: number; signal?: AbortSignal } = {}) {
for (let i = 0; ; i++) {
try { return await fn(signal!); }
catch (e) {
if (i >= attempts - 1 || !isRetryable(e) || signal?.aborted) throw e;
await sleep(base * 2 ** i + Math.random() * base); // экспоненциальная задержка с jitter
}
}
}
const isRetryable = (e: any) => !e.response || [408, 425, 429, 500, 502, 503, 504].includes(e.response.status);Правила: повторять только идемпотентные запросы и временные ошибки; учитывать Retry-After; ограничивать число попыток; не повторять 4xx (кроме 408/429).
Классификация ошибок и UX
| Ошибка | Реакция |
|---|---|
| Сеть недоступна/timeout | сообщение «нет соединения», кнопка «повторить», офлайн-режим |
| 400/422 | ошибки в полях формы |
| 401 | обновление токена/логин |
| 403 | сообщение о правах |
| 404 | пустое состояние/страница не найдена |
| 409 | конфликт: предложить обновить данные |
| 5xx | общее сообщение + traceId для поддержки |
Глобальная обработка: интерсептор + onError Vue Query + app.config.errorHandler; отправка в Sentry с контекстом.
Нюансы и подводные камни
- Гонки: более медленный старый запрос может прийти после нового — отменяйте/сопоставляйте.
- Отмена не откатывает действие на сервере (POST мог выполниться).
- Слепые повторы POST дублируют операции.
AbortErrorне должен показываться как ошибка пользователю.- Ретраи при 429 без учёта
Retry-Afterусугубляют нагрузку.
Практика
- Реализуйте поиск с отменой предыдущего запроса и обработкой
AbortError. - Напишите
withRetryс jitter и учётомRetry-After. - Классифицируйте ошибки и покажите различные сообщения.
Вопросы с ответами
Как отменить запрос?
AbortController: передатьsignalвfetch/Axios и вызватьabort().
Какие запросы можно повторять?
Идемпотентные при временных ошибках (сеть, 5xx, 429), с экспоненциальной задержкой и ограничением попыток.