← Назад к вопросам

Закэшировать можно GET или POST запрос

2.0 Middle🔥 281 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Кэширование GET и POST запросов: Различия и Практика

Это важный вопрос о HTTP и кэшировании. Ответ: по стандарту кэшируются GET, но не POST, хотя технически можно закэшировать любой метод.

HTTP Спецификация и Кэширование

1. GET Запросы (Идеальны для кэширования)

Почему GET кэшируется:

  • GET — безопасный метод (не изменяет данные)
  • GET — идемпотентный (одинаковый запрос = одинаковый результат)
  • Браузер и кэши по умолчанию кэшируют GET
// GET запрос
fetch('/api/users/1', {
  method: 'GET',
})
.then(r => r.json());

// Второй идентичный запрос
// Браузер может вернуть результат из кэша (если не истёк)
fetch('/api/users/1', {
  method: 'GET',
})
.then(r => r.json());

Контроль кэширования GET:

// Браузер кэширует по умолчанию
fetch('/api/data');

// Запретить кэширование
fetch('/api/data', {
  headers: {
    'Cache-Control': 'no-cache, no-store, must-revalidate',
  },
});

// Или через URL параметр (очень старый способ)
fetch(`/api/data?t=${Date.now()}`);

2. POST Запросы (Обычно НЕ кэшируются)

Почему POST не кэшируется:

  • POST — небезопасный метод (изменяет данные на сервере)
  • POST — не идемпотентный (разные результаты при повторе)
  • Браузер и прокси НЕ кэшируют POST по умолчанию
// POST не кэшируется
fetch('/api/users', {
  method: 'POST',
  body: JSON.stringify({ name: 'John' }),
})
.then(r => r.json());

// Каждый POST выполняется заново!
fetch('/api/users', {
  method: 'POST',
  body: JSON.stringify({ name: 'John' }),
})
.then(r => r.json());
// Создаст нового пользователя ВТОРОЙ раз (баг!)

3. Другие методы

МетодБезопасностьИдемпотентностьКэш по умолчанию
GETДаДаДа
HEADДаДаДа
OPTIONSДаДаДа
POSTНетНетНет
PUTНетДаНет
DELETEНетДаНет
PATCHНетНетНет

Практика: Кэширование в приложении

1. Встроенное кэширование браузера

// Браузер автоматически кэширует на основе Cache-Control
const fetchUsers = async () => {
  // Первый запрос: идёт на сервер
  const response = await fetch('/api/users', {
    method: 'GET',
    headers: {
      'Cache-Control': 'max-age=3600', // кэш на 1 час
    },
  });
  
  // Второй запрос в течение часа: из кэша браузера!
  const response2 = await fetch('/api/users');
};

2. Ручное кэширование (React пример)

interface CacheEntry<T> {
  data: T;
  timestamp: number;
  ttl: number; // time to live в ms
}

const apiCache = new Map<string, CacheEntry<any>>();

const cachedFetch = async <T>(
  url: string,
  options?: RequestInit,
  ttl: number = 5 * 60 * 1000 // 5 минут по умолчанию
): Promise<T> => {
  const cacheKey = `${options?.method || 'GET'}-${url}`;
  
  // Проверяем кэш
  const cached = apiCache.get(cacheKey);
  if (cached && Date.now() - cached.timestamp < cached.ttl) {
    console.log('Возвращаем из кэша:', cacheKey);
    return cached.data;
  }
  
  // Если кэша нет или он устарел
  const response = await fetch(url, options);
  const data = await response.json();
  
  // Сохраняем в кэш (только для GET)
  if (!options?.method || options.method === 'GET') {
    apiCache.set(cacheKey, {
      data,
      timestamp: Date.now(),
      ttl,
    });
  }
  
  return data;
};

// Использование
const users = await cachedFetch<User[]>('/api/users', undefined, 10 * 60 * 1000);
const updatedUsers = await cachedFetch<User[]>('/api/users'); // из кэша

3. React Query для кэширования

import { useQuery } from '@tanstack/react-query';

export function UserList() {
  // useQuery автоматически кэширует GET запросы
  const { data: users, isLoading } = useQuery({
    queryKey: ['users'], // уникальный ключ кэша
    queryFn: () => fetch('/api/users').then(r => r.json()),
    staleTime: 5 * 60 * 1000, // кэш считается "свежим" 5 мин
    cacheTime: 10 * 60 * 1000, // кэш удаляется через 10 мин
  });
  
  if (isLoading) return <div>Loading...</div>;
  
  return (
    <ul>
      {users?.map(u => <li key={u.id}>{u.name}</li>)}
    </ul>
  );
}

// Второй компонент с тем же queryKey: автоматически использует кэш
export function UserCount() {
  const { data: users } = useQuery({
    queryKey: ['users'], // ОДИНАКОВЫЙ ключ
    queryFn: () => fetch('/api/users').then(r => r.json()),
  });
  
  return <div>Всего пользователей: {users?.length}</div>;
}

4. SWR для кэширования

import useSWR from 'swr';

const fetcher = (url: string) => fetch(url).then(r => r.json());

export function UserProfile() {
  // SWR кэширует и периодически переполучает данные
  const { data, error } = useSWR('/api/user', fetcher, {
    revalidateOnFocus: false, // не переполучать при фокусе окна
    dedupingInterval: 60000, // 60 сек дедупликации
  });
  
  if (error) return <div>Ошибка!</div>;
  if (!data) return <div>Загрузка...</div>;
  
  return <div>Пользователь: {data.name}</div>;
}

// Когда нужны новые данные
const { data, mutate } = useSWR('/api/user', fetcher);

// Обновляем локально и на сервере
const updateUser = async (newData: any) => {
  await mutate(newData, false); // обновляем UI сразу (optimistic)
  await fetch('/api/user', {
    method: 'PUT',
    body: JSON.stringify(newData),
  });
  mutate(); // переполучаем с сервера
};

5. Service Worker для оффлайн кэширования

// sw.ts - Service Worker
self.addEventListener('fetch', (event: FetchEvent) => {
  if (event.request.method === 'GET') {
    event.respondWith(
      caches.match(event.request).then((response) => {
        if (response) {
          return response; // из кэша
        }
        
        return fetch(event.request).then((response) => {
          // Кэшируем успешные ответы
          if (response.status === 200) {
            const cloned = response.clone();
            caches.open('api-cache-v1').then((cache) => {
              cache.put(event.request, cloned);
            });
          }
          return response;
        });
      })
    );
  }
});

// Регистрируем Service Worker
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js');
}

6. Cache-Control Headers (бэкенд)

// Бэкенд должен установить правильные заголовки

// GET данные, которые можно кэшировать
app.get('/api/users', (req, res) => {
  res.set({
    'Cache-Control': 'public, max-age=3600', // кэш 1 час
    'ETag': '"123456"', // для валидации
  });
  res.json(users);
});

// Свежие данные, не кэшировать
app.get('/api/stock-price', (req, res) => {
  res.set({
    'Cache-Control': 'no-cache, no-store, must-revalidate',
  });
  res.json({ price: getCurrentPrice() });
});

// POST — никогда не кэшируется браузером
app.post('/api/users', (req, res) => {
  // Даже если не установим Cache-Control,
  // браузер не кэширует POST
  const newUser = createUser(req.body);
  res.status(201).json(newUser);
});

7. Проблема: Кэширование POST с GraphQL

GraphQL часто использует POST (даже для queries):

// GraphQL query через POST
fetch('/graphql', {
  method: 'POST', // POST по умолчанию не кэшируется
  body: JSON.stringify({
    query: `query { users { id name } }`,
  }),
})
.then(r => r.json());

// Решение: использовать Apollo Client, которое кэширует
import { ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
  cache: new InMemoryCache(), // встроенное кэширование
  // ...
});

Best Practices

✅ ПРАВИЛЬНО:

  • GET для чтения данных — кэшируется браузером
  • POST для создания — не кэшируется
  • PUT/DELETE для обновления — не кэшируются
  • Использовать React Query или SWR для кэширования
  • Установить правильные Cache-Control headers

❌ НЕПРАВИЛЬНО:

  • Полагаться на POST для кэширования
  • Использовать POST вместо GET для чтения
  • Забывать про Cache-Control headers
  • Кэшировать чувствительные данные (токены, пароли)

На собеседовании

Полный ответ должен показать:

  • Различие GET vs POST — по стандарту GET кэшируется, POST нет
  • Почему это так — безопасность и идемпотентность
  • Практические методы — React Query, SWR, Service Worker
  • Управление кэшем — Cache-Control headers
  • Проблемы в реальности — GraphQL, API запросы
Закэшировать можно GET или POST запрос | PrepBro