Обработка переподключений

RTK Query изначально спроектирован как слой управления серверным состоянием, а не как полноценный realtime-клиент, однако он учитывает нестабильность сети и умеет корректно реагировать на разрывы соединения. Обработка переподключений в контексте RTK Query включает несколько уровней: повторные HTTP-запросы, восстановление подписок, рефетч данных и интеграцию с пользовательской логикой через middleware и lifecycle-хуки.

Ключевой принцип заключается в том, что RTK Query не поддерживает «магическое» постоянное соединение, но предоставляет инструменты для построения устойчивого поведения поверх обычных запросов.


Сценарии потери соединения

Потеря соединения может происходить в разных формах:

  • временный обрыв сети (Wi-Fi переключение, мобильная сеть)
  • таймаут запроса на сервере
  • 5xx ошибки при перегрузке backend
  • полное отсутствие интернета
  • прерывание WebSocket/long-poll соединения (если используется кастомная интеграция)

RTK Query рассматривает все эти ситуации как ошибки запроса, которые могут быть обработаны через стандартный механизм baseQuery.


Базовый механизм повторных попыток (retry)

RTK Query предоставляет утилиту retry из @reduxjs/toolkit/query/react, которая добавляет автоматические повторные попытки к baseQuery.

import { createApi, fetchBaseQuery, retry } from '@reduxjs/toolkit/query/react';

const baseQuery = retry(
  fetchBaseQuery({
    baseUrl: '/api',
  }),
  {
    maxRetries: 3,
  }
);

Поведение retry-обёртки

  • автоматический повтор запроса при ошибке сети
  • экспоненциальная задержка между попытками
  • контроль максимального числа повторов
  • возможность кастомизации через retryCondition

Условная логика повторов

Не все ошибки должны приводить к повторным запросам. Например, 400 Bad Request или 404 Not Found не являются сетевыми сбоями.

const baseQuery = retry(
  fetchBaseQuery({ baseUrl: '/api' }),
  {
    maxRetries: 2,
    retryCondition: (error) => {
      return (
        error.status === 'FETCH_ERROR' ||
        error.status === 'TIMEOUT_ERROR' ||
        error.status >= 500
      );
    },
  }
);

Здесь важно разделять:

  • сетевые ошибки (подходят для retry)
  • клиентские ошибки (не требуют retry)
  • серверные ошибки (временные, могут требовать retry)

Поведение RTK Query при восстановлении сети

Когда соединение восстанавливается, RTK Query сам по себе не «знает» об этом событии. Однако встроенный механизм подписок и refetching позволяет реализовать автоматическое восстановление данных.

Ключевые триггеры:

  • повторное монтирование компонента
  • изменение аргументов запроса
  • фокус окна (focus refetch)
  • восстановление сети (online refetch)

Refetch при восстановлении сети

RTK Query поддерживает автоматический refetch при возвращении онлайн-статуса.

export const api = createApi({
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  refetchOnReconnect: true,
  endpoints: (builder) => ({
    getUsers: builder.query({
      query: () => 'users',
    }),
  }),
});

Что происходит при refetchOnReconnect

  • подписка на window.online событие
  • при восстановлении сети активные query помечаются как устаревшие
  • выполняется повторный запрос
  • обновляется cache

Refetch при фокусе окна

Хотя это не напрямую связано с переподключением, в реальных приложениях часто используется вместе:

export const api = createApi({
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  refetchOnFocus: true,
});

Сценарий:

  • пользователь теряет интернет
  • переключается на другую вкладку
  • возвращается после восстановления сети
  • данные автоматически синхронизируются

Интеграция с жизненным циклом подписок

RTK Query использует систему подписок на кешированные данные. При разрыве соединения подписка не уничтожается, но может стать «устаревшей».

Состояния подписки:

  • active (есть подписчики)
  • inactive (нет подписчиков)
  • fetching (идёт запрос)
  • fulfilled / rejected (результат)

При восстановлении сети активные подписки могут инициировать повторную загрузку данных.


Обработка переподключений через middleware

Для более сложных сценариев используется middleware на уровне Redux.

const reconnectMiddleware = (api) => (next) => (action) => {
  if (action.type === 'network/reconnected') {
    api.util.invalidateTags(['User', 'Post']);
  }
  return next(action);
};

invalidateTags как механизм синхронизации

После переподключения часто требуется:

  • сбросить устаревший кеш
  • инициировать refetch связанных данных
  • синхронизировать критические сущности

WebSocket и RTK Query

RTK Query не управляет WebSocket напрямую, но интеграция возможна через onCacheEntryAdded.

getMessages: builder.query({
  query: () => 'messages',
  async onCacheEntryAdded(
    arg,
    { updateCachedData, cacheDataLoaded, cacheEntryRemoved }
  ) {
    const ws = new WebSocket('wss://example.com');

    try {
      await cacheDataLoaded;

      ws.onmess age = (event) => {
        const data = JSON.parse(event.data);
        updateCachedData((draft) => {
          draft.push(data);
        });
      };
    } catch {
      ws.close();
    }

    await cacheEntryRemoved;
    ws.close();
  },
});

Переподключение WebSocket

RTK Query не восстанавливает WebSocket автоматически. Обычно применяется внешняя логика:

  • повторное создание соединения при onclose
  • exponential backoff
  • синхронизация через invalidateTags

Backoff стратегия при переподключении

Для стабильных приложений используется стратегия экспоненциальной задержки:

  • 1 попытка: сразу
  • 2 попытка: 1–2 секунды
  • 3 попытка: 4 секунды
  • 4 попытка: 8 секунд

RTK Query через retry реализует аналогичную модель для HTTP-запросов, но WebSocket требует ручной реализации.


Контроль повторных запросов через lifecycle hooks

RTK Query предоставляет hooks для контроля поведения:

  • onQueryStarted
  • onCacheEntryAdded
  • onQueryFulfilled

Пример ручного retry:

getData: builder.query({
  query: () => '/data',
  async onQueryStarted(arg, { dispatch, queryFulfilled }) {
    try {
      await queryFulfilled;
    } catch (err) {
      setTimeout(() => {
        dispatch(api.endpoints.getData.initiate(arg));
      }, 2000);
    }
  },
});

Этот подход используется, когда стандартный retry недостаточен.


Поведение кэша при переподключении

Кэш RTK Query остаётся в памяти Redux Store и не сбрасывается при потере соединения.

Важно учитывать:

  • stale данные могут отображаться до refetch
  • автоматическая синхронизация зависит от настроек refetchOnReconnect
  • keepUnusedDataFor влияет на срок жизни кеша

Комбинированная стратегия устойчивости

В реальных приложениях обработка переподключений строится на комбинации:

  • retry для HTTP ошибок
  • refetchOnReconnect для восстановления данных
  • invalidateTags для принудительной синхронизации
  • WebSocket reconnect logic для realtime данных
  • middleware для глобальных событий сети

Типичный сценарий восстановления данных

  1. соединение пропадает
  2. RTK Query фиксирует ошибку запроса
  3. retry выполняет ограниченные повторные попытки
  4. пользователь восстанавливает сеть
  5. срабатывает refetchOnReconnect
  6. активные query перезапрашиваются
  7. кеш обновляется
  8. UI синхронизируется с сервером

Ограничения модели переподключений

Несмотря на встроенные механизмы, RTK Query не решает:

  • долгоживущие WebSocket reconnection стратегии
  • дедупликацию сложных offline-очередей
  • офлайн-first архитектуры с локальной записью изменений
  • конфликт-резолюцию при изменении данных в офлайн режиме

Эти задачи требуют дополнительного слоя архитектуры поверх RTK Query.


Практическая структура устойчивого слоя

В зрелых приложениях слой работы с переподключениями обычно включает:

  • API slice RTK Query
  • network status slice (online/offline)
  • middleware событий сети
  • глобальный retry policy
  • WebSocket manager

RTK Query в этой структуре выступает как слой синхронизации серверного состояния после восстановления соединения.