TypeError: Failed to fetch — что означает и как исправить

JavaScript Автор: Среда и версия: Fetch API в современных браузерах
содержание

Дословно Failed to fetch — «не удалось получить». Браузер сообщает этой фразой ровно одно: доступного ответа не получено. Запрос мог быть заблокирован ещё до отправки. Технически — fetch() не вернул коду доступный объект Response.

Причин почти всегда пять: неверный URL, недоступная сеть, проблема с TLS или mixed content, запрет по CORS или CSP, отменённый запрос. Откройте Network и Console: если HTTP-статуса у запроса нет вообще, причина среди этих пяти.

Статусы 404, 401 и 500 сами по себе не отклоняют fetch(). Если ответ доступен коду, промис выполняется с Response, а статус проверяют через response.ok или response.status.

JavaScript / 01

HTTP-ошибка и отклонённый Promise — разные исходы

HTTP-ошибка и отклонённый Promise — разные исходы01 / вход 02 / операция 03 / результат Response доступен status 200–299 response.ok: true Response доступен status 404 / 500 response.ok: false Promise rejected Failed to fetch проверь сеть и CORS01 / вход02 / операция03 / результатResponse доступенstatus 200–299response.ok: trueResponse доступенstatus 404 / 500response.ok: falsePromise rejectedFailed to fetchпроверь сеть и CORS
Статус 404 приходит по ветке Response. Failed to fetch относится к ветке без доступного ответа, а точную причину обычно показывает не JavaScript, а DevTools.

Почему fetch не считает 404 и 500 сетевой ошибкой

Проверяйте response.ok до чтения тела. Это свойство равно true для статусов от 200 до 299:

async function getJson(url, options) {
  const response = await fetch(url, options)

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`)
  }

  return response.json()
}

Теперь у приложения остаются разные сигналы:

  • Error: HTTP 404 означает, что сервер ответил и код сам отклонил такой статус;
  • TypeError: Failed to fetch означает, что fetch() не вернул доступный Response;
  • SyntaxError при response.json() означает, что ответ пришёл, но его тело не оказалось допустимым JSON.

Первый случай — обычный HTTP-ответ: 401 требует проверить учётные данные, 404 — адрес и наличие ресурса, 500 означает серверную ошибку. Сам статус 500 не делает повтор безопасным: сервер мог уже выполнить операцию. Повторяйте запрос только при идемпотентной операции, серверной защите от дублирования или подтверждении, что первый запрос не был применён. Что значит каждый класс и какой возвращать самому, разобрано в справочнике кодов ответов HTTP.

Не заменяйте все три случая одной строкой throw new Error('Failed to fetch'): она стирает исходную ошибку и делает диагностику сложнее.

Как исправить Failed to fetch: диагностика по шагам

Откройте DevTools до повторения ошибки. На вкладке Network включите сохранение журнала при переходах, очистите список и снова выполните действие. Затем проверьте наблюдения по порядку.

1. Какой URL получил браузер

Откройте запись запроса и сверьте Request URL, схему, домен, порт и путь. Относительный адрес вычисляется от базового URL документа, поэтому /api/users и api/users на вложенной странице могут привести к разным путям.

Перед fetch() адрес можно проверить штатным классом URL:

const requestUrl = new URL(endpoint, document.baseURI)
console.log(requestUrl.href)

const response = await fetch(requestUrl)

Если new URL() выбросил TypeError, исправьте входное значение. Если адрес корректен, но ведёт не на тот хост или порт, исправьте базовый URL или конфигурацию окружения. Не добавляйте протокол, порт и слеши строковыми склейками.

Если строки запроса в Network нет, убедитесь, что выполнение дошло до fetch(): поставьте breakpoint на эту строку или временно выведите вычисленный URL. Синтаксическая ошибка await is only valid in async functions останавливает код раньше, поэтому к Failed to fetch не относится.

2. Есть ли HTTP-статус

Числовой статус 4xx или 5xx подтверждает ответ сервера, но ещё не гарантирует доступ JavaScript к нему: отдельно проверьте Console на CORS. Если CORS-сообщения нет и код получил Response, возвращайтесь к проверке response.ok.

Статусы (failed), CORS error и (blocked:…) в Network указывают на ветку без обычного ответа для кода. Откройте запись и посмотрите Headers, Initiator и Timing, затем прочитайте соседнее сообщение в Console. Текст статуса и набор вкладок зависят от браузера, поэтому не стройте логику приложения на строках ошибок DevTools.

3. На каком участке произошёл сбой

НаблюдениеЧто проверитьИсправление
Ошибка разрешения имени, соединения или ожиданияДомен, VPN или прокси, доступность API, портИсправить адрес или сетевой маршрут; проверить, что сервис запущен
Ошибка сертификата или TLSСрок и цепочку сертификата, имя хостаИсправить сертификат на сервере; не отключать проверку TLS у пользователей
HTTPS-страница вызывает HTTP APIВ Console есть сообщение о mixed contentПеревести API и URL запроса на HTTPS
Console сообщает о connect-srcЗаголовок Content-Security-Policy документаДобавить нужный origin в серверную политику connect-src
Console сообщает о CORSOrigin, preflight OPTIONS и ответные CORS-заголовкиИсправить разрешения на сервере по точной причине из Console
Запрос отменёнПереданный AbortSignal, таймер и вызов abort()Считать ожидаемую отмену отдельным исходом, а не сетевым сбоем
Ошибка исчезает в чистом профилеБлокировщик рекламы, privacy-расширение или защитное ПОНайти правило или домен, который блокируется; не просить всех отключать защиту

Адрес API можно открыть в отдельной вкладке только как дополнительную проверку DNS, TLS и доступности обычного GET. Такая проверка не повторяет метод, заголовки, тело, CSP и CORS исходного fetch(), поэтому успешная страница не доказывает, что запрос из приложения разрешён.

Сеть, DNS, TLS и офлайн-режим

При сетевом отказе браузер может показать в Network более точную фазу: DNS lookup, соединение, согласование TLS или ожидание ответа. Сам объект ошибки в JavaScript не обязан раскрывать эту фазу. Записывайте для поддержки URL без секретных параметров, время сбоя и наблюдение из DevTools, но не пытайтесь извлечь причину разбором текста TypeError.

Проверьте, не выбран ли в Network режим Offline, и открывается ли заведомо доступный ресурс того же приложения. navigator.onLine годится только как подсказка:

if (!navigator.onLine) {
  showNotice('Похоже, устройство не подключено к сети')
}

Значение true не подтверждает доступ к интернету или конкретному API: устройство может видеть локальную сеть, но не сервер. Поэтому не отменяйте запросы только на основании navigator.onLine.

Mixed content, CSP и CORS

Если страница открыта по HTTPS, запрос fetch() к обычному удалённому HTTP-origin относится к блокируемому смешанному контенту. Loopback-адреса вроде localhost и 127.0.0.1 браузер может считать потенциально доверенными, поэтому проверяйте фактическое сообщение в Console. Для удалённого API исправление находится в адресе и настройке сервера: он должен быть доступен по HTTPS. Отключение защиты браузера скрывает дефект только на машине разработчика.

CSP ограничивает подключения директивой connect-src. При блокировке Console указывает нарушенную директиву и адрес. Разрешайте только нужный origin в политике, которую сервер отдаёт для документа; try…catch не может обойти CSP.

CORS относится к чтению ответа от другого origin. По соображениям безопасности JavaScript получает общий сбой, а подробности остаются в Console. Не лечите его опцией mode: 'no-cors': она возвращает непрозрачный ответ со статусом 0, без доступных заголовков и тела. Разбор preflight и серверных заголовков есть в статье blocked by CORS policy.

Отмена запроса и расширения

Запрос с AbortSignal может завершиться потому, что код сам вызвал abort(), сработал таймер или компонент уничтожился. Нативный AbortController обычно отклоняет промис с DOMException по имени AbortError, но обёртка над Fetch может заменить её общим сообщением. Проверьте сигнал до повтора запроса:

try {
  return await getJson('/api/profile', { signal })
} catch (error) {
  if (signal.aborted) {
    console.info('Запрос отменён:', signal.reason)
    return null
  }

  throw error
}

Если приложение не отменяло запрос, воспроизведите проблему в чистом профиле браузера. Исчезновение ошибки указывает на локальное расширение, фильтр DNS, антивирус или прокси, но не показывает виновника автоматически. Возвращайте расширения по одному и сверяйте домен или правило блокировки.

Что сообщать пользователю

Интерфейс не должен обещать точную причину, которой у кода нет. Для неожиданного отказа достаточно сообщения «Не удалось связаться с сервером»; кнопку повтора добавляйте, только если операция допускает безопасный повтор. Ожидаемую отмену показывать как ошибку не нужно, а HTTP-ответы следует обрабатывать по их статусу.

В журнал разработчика передавайте исходный объект ошибки через cause или систему сбора ошибок, не включая токены и персональные данные из URL. Подробности CORS, CSP, DNS и TLS всё равно придётся сверять по DevTools или серверным журналам: Fetch намеренно не даёт скрипту полный отчёт о защитных проверках браузера.

Источники