← Назад к вопросам
Всегда ли статус 200 означает успешное выполнение запроса
2.0 Middle🔥 191 комментариев
#HTML и CSS
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Всегда ли статус 200 означает успешное выполнение запроса
Ответ: Нет, не всегда
Статус 200 OK — это только HTTP-уровневый ответ, который означает, что сервер успешно обработал запрос и отправил ответ. Однако это не гарантирует, что запрос выполнен корректно с бизнес-точки зрения. Это частая ошибка frontend разработчиков, которые доверяют только HTTP статусам.
HTTP статусы vs Бизнес-статусы
Что значит статус 200
HTTP 200 OK означает:
✅ Запрос был синтаксически корректен
✅ Сервер его обработал
✅ Ответ был успешно отправлен
❌ НЕ означает:
- Операция выполнена успешно (бизнес-логика)
- Данные актуальны и корректны
- Авторизация пройдена
- Никаких ошибок не было
Реальные примеры
// Пример 1: 200 с ошибкой в теле ответа
const response = await fetch('/api/v1/questions', {
method: 'POST',
body: JSON.stringify({ title: 'Question' })
});
if (response.status === 200) {
const data = await response.json();
// ❌ Ошибка! Не проверили data.success
// Сервер может вернуть: { success: false, error: 'Invalid title' }
console.log(data.id); // undefined!
}
// ✅ Правильно: проверяем и HTTP статус, и тело ответа
if (response.ok && response.status === 200) {
const data = await response.json();
if (data.success === true) {
console.log(data.id);
} else {
console.error('Business error:', data.error);
}
}
Пример 2: 200 с неполными данными
// ❌ Неправильно: доверяем только 200
const user = await fetch('/api/v1/users/me').then(r => r.json());
console.log(user.email); // undefined — сервер забыл поле!
// ✅ Правильно: валидируем структуру данных
interface User {
id: string;
email: string;
name: string;
}
function validateUser(data: unknown): data is User {
return (
typeof data === 'object' &&
data !== null &&
'id' in data &&
'email' in data &&
'name' in data &&
typeof (data as Record<string, unknown>).email === 'string'
);
}
const response = await fetch('/api/v1/users/me');
if (response.ok) {
const data = await response.json();
if (validateUser(data)) {
console.log(data.email);
} else {
console.error('Invalid response structure');
}
}
Различные сценарии HTTP 200
Сценарий 1: JSON с флагом ошибки
// API возвращает 200, но в теле сообщение об ошибке
const response = await fetch('/api/v1/login', {
method: 'POST',
body: JSON.stringify({ username: 'john', password: 'wrong' })
});
const data = await response.json();
// { "success": false, "error": "Invalid password" }
// ❌ Неправильно: только проверка статуса
if (response.status === 200) {
localStorage.setItem('token', data.token); // token undefined!
}
// ✅ Правильно: проверяем бизнес-флаг
if (response.status === 200 && data.success === true) {
localStorage.setItem('token', data.token);
} else {
showError(data.error || 'Unknown error');
}
Сценарий 2: Пустой или невалидный JSON
// ❌ Неправильно
const response = await fetch('/api/v1/data');
const data = await response.json(); // Может бросить ошибку!
console.log(data.items); // undefined если пусто
// ✅ Правильно: обрабатываем ошибки парсинга
const response = await fetch('/api/v1/data');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
try {
const data = await response.json();
if (Array.isArray(data.items)) {
return data.items;
} else {
throw new Error('Invalid response structure');
}
} catch (err) {
console.error('JSON parse error:', err);
throw err;
}
Сценарий 3: 200 но с redirect
// ❌ Это может быть в старом коде, но не в fetch (он автоматически следует редиректам)
const response = await fetch('/api/v1/old-endpoint');
if (response.status === 200) {
// fetch автоматически следует 301/302 редиректам
// поэтому вы получите 200 с содержимым конечной страницы
console.log(response.url); // Может отличаться от исходного URL
}
Правильная обработка HTTP запросов
interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
code?: string;
}
interface Question {
id: string;
title: string;
difficulty: 'easy' | 'medium' | 'hard';
}
async function fetchQuestion(id: string): Promise<Question> {
// Этап 1: Проверяем HTTP статус
const response = await fetch(`/api/v1/questions/${id}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// Этап 2: Парсим JSON
let data: unknown;
try {
data = await response.json();
} catch (err) {
throw new Error('Failed to parse JSON response');
}
// Этап 3: Проверяем бизнес-уровневый успех
if (typeof data !== 'object' || !data) {
throw new Error('Invalid response structure');
}
const apiResponse = data as ApiResponse<Question>;
if (!apiResponse.success) {
throw new Error(`API Error: ${apiResponse.error || 'Unknown'}`);
}
// Этап 4: Валидируем данные
const question = apiResponse.data;
if (!question || typeof question !== 'object') {
throw new Error('No data in response');
}
if (
!('id' in question) ||
!('title' in question) ||
!('difficulty' in question)
) {
throw new Error('Question missing required fields');
}
return question as Question;
}
// Использование
try {
const question = await fetchQuestion('123');
console.log('Success:', question);
} catch (err) {
console.error('Failed to fetch question:', err);
// Показываем пользователю понятное сообщение об ошибке
}
HTTP статусы и их реальное значение
2xx — Успешно обработано
200 OK — запрос обработан, но проверьте тело ответа!
201 Created — ресурс создан
204 No Content — успешно, но нет данных
206 Partial Content — частичное содержимое (диапазоны байт)
3xx — Редирект
301 Moved Permanently — перемещено (fetch следует автоматически)
304 Not Modified — кэшированные данные актуальны
4xx — Ошибка клиента
400 Bad Request — неправильный формат
401 Unauthorized — нужна авторизация
403 Forbidden — доступ запрещён
404 Not Found — не найдено
429 Too Many Requests — лимит запросов
5xx — Ошибка сервера
500 Internal Server Error — внутренняя ошибка
503 Service Unavailable — сервис недоступен
Лучшие практики
// ✅ Создай хелпер для API запросов
interface ApiOptions extends RequestInit {
timeout?: number;
}
async function apiCall<T>(
url: string,
options: ApiOptions = {}
): Promise<T> {
const { timeout = 5000, ...fetchOptions } = options;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
...fetchOptions,
signal: controller.signal
});
// Проверяй HTTP статус
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
// Парсь JSON с обработкой ошибок
const data = await response.json();
// Проверяй структуру (используй Zod, Yup или io-ts)
return data as T;
} finally {
clearTimeout(timeoutId);
}
}
// ✅ Используй Zod для валидации
import { z } from 'zod';
const QuestionSchema = z.object({
id: z.string().uuid(),
title: z.string().min(1),
difficulty: z.enum(['easy', 'medium', 'hard'])
});
type Question = z.infer<typeof QuestionSchema>;
const data = await apiCall('/api/v1/questions/123');
const validated = QuestionSchema.parse(data); // Выбросит ошибку если невалидно
Заключение
Статус 200 — это только начало. Правильная обработка API запросов требует:
- Проверка HTTP статуса (
response.okилиstatus >= 200 && status < 300) - Обработка ошибок парсинга (JSON может быть невалидным)
- Проверка бизнес-уровня (может быть флаг success, error code и т.д.)
- Валидация данных (Zod, TypeGuards, или ручная проверка)
- Обработка таймаутов и сетевых ошибок
Только полная цепочка проверок гарантирует, что запрос действительно выполнен успешно.