Кеширование результатов роутинга

Кеширование в контексте роутинга позволяет значительно повысить производительность приложения, особенно когда маршруты выполняют сложные операции или обращаются к внешним ресурсам. В Universal Router кеширование можно реализовать на нескольких уровнях: кеширование результатов обработчиков, кеширование промисов и сохранение промежуточных вычислений для повторного использования.


Механизм кеширования обработчиков

Каждый маршрут в Universal Router описывается объектом с ключами path и action. Если результат работы action не меняется при одинаковых входных данных, его можно закешировать. Простейшая реализация кеша может выглядеть так:

import UniversalRouter from 'universal-router';

const cache = new Map();

const routes = [
  {
    path: '/user/:id',
    async action(context) {
      const cacheKey = `user:${context.params.id}`;
      if (cache.has(cacheKey)) {
        return cache.get(cacheKey);
      }
      const user = await fetchUserFromAPI(context.params.id);
      cache.set(cacheKey, user);
      return user;
    }
  }
];

const router = new UniversalRouter(routes);

Ключевые моменты:

  • cacheKey формируется на основе параметров маршрута.
  • Кеширование работает для асинхронных и синхронных обработчиков.
  • Важно правильно определить время жизни кеша, чтобы данные не устаревали.

Кеширование промисов

Universal Router полностью поддерживает асинхронные обработчики через промисы. Вместо кеширования окончательного результата можно кешировать сам промис, что предотвращает многократные параллельные запросы к одному ресурсу:

const promiseCache = new Map();

async function fetchUser(id) {
  if (!promiseCache.has(id)) {
    promiseCache.set(id, fetchUserFromAPI(id));
  }
  return promiseCache.get(id);
}

const routes = [
  {
    path: '/user/:id',
    action: ({ params }) => fetchUser(params.id)
  }
];

Преимущества:

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

Условное кеширование

Иногда результат маршрута зависит не только от параметров URL, но и от состояния приложения или времени. В таких случаях можно использовать условное кеширование:

const routes = [
  {
    path: '/posts',
    async action(context) {
      const cacheKey = 'posts';
      const cacheData = cache.get(cacheKey);
      
      if (cacheData && Date.now() - cacheData.timestamp < 60000) {
        return cacheData.value;
      }

      const posts = await fetchPosts();
      cache.set(cacheKey, { value: posts, timestamp: Date.now() });
      return posts;
    }
  }
];

Особенности реализации:

  • Хранение метки времени (timestamp) позволяет реализовать автоматический ttl (time-to-live).
  • Можно комбинировать с параметрами запроса для динамических маршрутов.

Интеграция с внешними кешами

Для крупных приложений часто имеет смысл интегрировать Universal Router с внешними кешами, такими как Redis или localStorage. Это позволяет:

  • Делать кеш доступным между сессиями и страницами.
  • Уменьшить нагрузку на API при частом посещении одних и тех же маршрутов.

Пример с localStorage:

function getCached(key) {
  const cached = localStorage.getItem(key);
  if (!cached) return null;
  const data = JSON.parse(cached);
  if (Date.now() - data.timestamp > 300000) return null; // 5 минут ttl
  return data.value;
}

function setCached(key, value) {
  localStorage.setItem(key, JSON.stringify({ value, timestamp: Date.now() }));
}

const routes = [
  {
    path: '/dashboard',
    async action() {
      const cacheKey = 'dashboard';
      const cached = getCached(cacheKey);
      if (cached) return cached;
      const data = await fetchDashboardData();
      setCached(cacheKey, data);
      return data;
    }
  }
];

Стратегии кеширования

Выбор стратегии зависит от характера маршрута и приложения:

  1. Полное кеширование – результат всегда одинаковый, можно хранить навсегда.
  2. Временное кеширование – данные обновляются через фиксированные интервалы (TTL).
  3. Кеширование промисов – предотвращает дублирование асинхронных вызовов.
  4. Условное кеширование – результат зависит от состояния или параметров запроса.

Рекомендации:

  • Использовать кеш для маршрутов с тяжелыми вычислениями.
  • Ограничивать размер кеша и очищать устаревшие данные.
  • Для асинхронных маршрутов предпочтительнее кешировать промисы, чтобы избежать гонок.

Обработка ошибок при кешировании

Кеширование должно учитывать возможность ошибок при асинхронных вызовах:

const routes = [
  {
    path: '/profile/:id',
    async action({ params }) {
      const cacheKey = `profile:${params.id}`;
      try {
        if (cache.has(cacheKey)) return cache.get(cacheKey);
        const profile = await fetchProfile(params.id);
        cache.set(cacheKey, profile);
        return profile;
      } catch (error) {
        console.error('Ошибка при загрузке профиля:', error);
        throw error; // Не сохраняем в кеш при ошибке
      }
    }
  }
];
  • Никогда не кешировать неуспешные промисы или исключения.
  • Логирование ошибок помогает выявить проблемные маршруты.

Кеширование в Universal Router — это гибкий инструмент, который позволяет улучшить производительность, снизить нагрузку на сервер и уменьшить задержки при навигации. Использование кешей промисов, TTL, условного хранения и интеграции с внешними хранилищами обеспечивает высокую эффективность работы маршрутов даже в сложных приложениях.