Кэширование запросов

Slim.js — это легковесная JavaScript-библиотека для создания компонентного интерфейса, которая делает акцент на простоте реактивного программирования и управлении состоянием. Одной из ключевых задач при работе с внешними данными является эффективное кэширование запросов, позволяющее минимизировать нагрузку на сервер и ускорить отклик интерфейса.

Реактивные свойства и хранение данных

Slim.js использует реактивные свойства (@state и @prop) для управления состоянием компонента. Кэширование запросов напрямую связано с этим механизмом:

import { Slim, html, state } from 'slim-js';

class UserList extends Slim {
  @state users = [];
  @state cache = {};

  connectedCallback() {
    super.connectedCallback();
    this.loadUsers();
  }

  async loadUsers() {
    const url = '/api/users';
    if (this.cache[url]) {
      this.users = this.cache[url];
      return;
    }
    const response = await fetch(url);
    const data = await response.json();
    this.cache[url] = data;
    this.users = data;
  }

  render() {
    return html`
      <ul>
        ${this.users.map(user => html`<li>${user.name}</li>`)}
      </ul>
    `;
  }
}

customElements.define('user-list', UserList);

В этом примере объект cache служит локальным хранилищем для результатов запросов. Перед выполнением сетевого запроса происходит проверка, сохранены ли данные, что предотвращает лишние вызовы fetch.

Временное кэширование и устаревание данных

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

@state cache = {};

async loadUsers() {
  const url = '/api/users';
  const now = Date.now();

  if (this.cache[url] && now - this.cache[url].timestamp < 60000) { // 60 секунд
    this.users = this.cache[url].data;
    return;
  }

  const response = await fetch(url);
  const data = await response.json();
  this.cache[url] = { data, timestamp: now };
  this.users = data;
}

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

Кэширование с использованием ключей

В сложных приложениях часто нужны динамические запросы с параметрами. Для этого используется система ключей, формируемых на основе URL и параметров запроса:

getCacheKey(url, params = {}) {
  const query = new URLSearchParams(params).toString();
  return `${url}?${query}`;
}

async loadUsers(params) {
  const key = this.getCacheKey('/api/users', params);
  if (this.cache[key]) {
    this.users = this.cache[key];
    return;
  }

  const response = await fetch(`/api/users?${new URLSearchParams(params)}`);
  const data = await response.json();
  this.cache[key] = data;
  this.users = data;
}

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

Интеграция с реактивным обновлением компонентов

Slim.js автоматически отслеживает изменения реактивных свойств. После обновления кэша достаточно присвоить новое значение @state, чтобы интерфейс обновился:

this.users = this.cache[key]; // Интерфейс обновится автоматически

Это упрощает архитектуру приложения: нет необходимости вручную триггерить ререндер или управлять DOM.

Кэширование с внешними библиотеками

Для сложных сценариев можно сочетать Slim.js с библиотеками кэширования, например localforage или IndexedDB, для сохранения данных между сессиями:

import localforage from 'localforage';

async loadUsers() {
  const url = '/api/users';
  const cached = await localforage.getItem(url);
  if (cached) {
    this.users = cached;
    return;
  }

  const response = await fetch(url);
  const data = await response.json();
  await localforage.setItem(url, data);
  this.users = data;
}

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

Оптимизация и предотвращение дублирующихся запросов

Частая ошибка при работе с асинхронными запросами — многократный вызов одного и того же запроса, если он ещё не завершён. Это можно решить с помощью промисов:

@state pendingRequests = {};

async loadUsers() {
  const url = '/api/users';
  
  if (this.cache[url]) {
    this.users = this.cache[url];
    return;
  }

  if (!this.pendingRequests[url]) {
    this.pendingRequests[url] = fetch(url)
      .then(res => res.json())
      .then(data => {
        this.cache[url] = data;
        delete this.pendingRequests[url];
        return data;
      });
  }

  this.users = await this.pendingRequests[url];
}

Такой механизм гарантирует, что один запрос выполняется только один раз, даже если компонент несколько раз инициирует его одновременно.

Выводы по структуре кэша

Эффективное кэширование в Slim.js строится на нескольких принципах:

  • Хранение данных в реактивных свойствах компонента (@state)
  • Контроль времени жизни кэша для актуальности информации
  • Формирование уникальных ключей для разных вариантов запроса
  • Предотвращение дублирования запросов через систему pendingRequests
  • Возможность интеграции с внешними хранилищами для долговременного кэширования

Эти подходы позволяют создавать высокопроизводительные интерфейсы, минимизировать сетевые обращения и сохранять чистую, реактивную архитектуру компонентов.