При использовании Tom Select в режиме загрузки данных с сервера
основная нагрузка ложится на функцию load(query, callback),
которая вызывается при каждом вводе пользователя. Без кеширования
одинаковые запросы повторяются, создавая избыточные сетевые обращения,
увеличивая задержки интерфейса и нагрузку на API.
Кеширование в данном контексте решает три ключевые задачи:
Особенно критично это поведение при поиске по справочникам, API с лимитами или при работе с медленными источниками данных.
loadTom Select не навязывает встроенный слой кеширования, но позволяет
легко реализовать его поверх стандартного load.
Классическая схема основана на использовании in-memory структуры:
const cache = new Map();
new TomSelect("#select", {
valueField: "id",
labelField: "title",
searchField: "title",
load: function(query, callback) {
if (cache.has(query)) {
callback(cache.get(query));
return;
}
fetch(`/api/search?q=${encodeURIComponent(query)}`)
.then(res => res.json())
.then(data => {
cache.set(query, data);
callback(data);
})
.catch(() => callback());
}
});
Здесь ключом кеша выступает строка запроса query. Это
простая, но эффективная стратегия для большинства поисковых
сценариев.
Одной из распространённых проблем становится неоднозначность
пользовательского ввода. Запросы "Apple",
"apple" и " apple " с точки зрения сервера
могут быть эквивалентны, но в кеше будут различаться.
Решение заключается в нормализации ключа:
function normalizeQuery(query) {
return query.trim().toLowerCase();
}
Использование:
const key = normalizeQuery(query);
Дополнительно могут применяться:
В реальных приложениях данные могут изменяться, поэтому простого кеша недостаточно. Добавляется механизм времени жизни (TTL — time to live).
const cache = new Map();
const TTL = 60 * 1000; // 1 минута
function setCache(key, data) {
cache.set(key, {
data,
time: Date.now()
});
}
function getCache(key) {
const entry = cache.get(key);
if (!entry) return null;
const isExpired = Date.now() - entry.time > TTL;
if (isExpired) {
cache.delete(key);
return null;
}
return entry.data;
}
Интеграция в Tom Select:
load: function(query, callback) {
const key = normalizeQuery(query);
const cached = getCache(key);
if (cached) {
callback(cached);
return;
}
fetch(`/api/search?q=${encodeURIComponent(query)}`)
.then(res => res.json())
.then(data => {
setCache(key, data);
callback(data);
});
}
При постраничной загрузке одного ключа query
недостаточно. Данные зависят от page, limit и
других параметров.
Расширенная стратегия ключа:
function makeCacheKey(query, page) {
return `${normalizeQuery(query)}::page=${page}`;
}
Пример использования:
load: function(query, callback) {
const page = this.currentPage || 1;
const key = makeCacheKey(query, page);
const cached = getCache(key);
if (cached) {
callback(cached);
return;
}
fetch(`/api/search?q=${query}&page=${page}`)
.then(r => r.json())
.then(data => {
setCache(key, data);
callback(data);
});
}
При заранее известных сценариях можно уменьшить задержку первого ввода, выполнив предзагрузку популярных запросов.
const preloadQueries = ["a", "b", "c"];
preloadQueries.forEach(q => {
fetch(`/api/search?q=${q}`)
.then(r => r.json())
.then(data => setCache(normalizeQuery(q), data));
});
Такая стратегия особенно полезна для справочников с предсказуемыми значениями или часто используемыми фильтрами.
При длительной работе интерфейса Map-кеш может расти бесконечно. Для предотвращения деградации памяти используется ограничение размера.
const cache = new Map();
const MAX_CACHE_SIZE = 100;
function setCache(key, data) {
if (cache.has(key)) {
cache.delete(key);
}
cache.set(key, {
data,
time: Date.now()
});
if (cache.size > MAX_CACHE_SIZE) {
const oldestKey = cache.keys().next().value;
cache.delete(oldestKey);
}
}
Такой подход приближён к LRU-логике без полной реализации алгоритма вытеснения.
Tom Select может ограничивать частоту вызова load через
параметр loadThrottle. Однако кеширование и throttling
решают разные задачи:
Совместное использование:
new TomSelect("#select", {
loadThrottle: 300,
load: function(query, callback) {
const key = normalizeQuery(query);
const cached = getCache(key);
if (cached) {
callback(cached);
return;
}
fetch(`/api/search?q=${query}`)
.then(r => r.json())
.then(data => {
setCache(key, data);
callback(data);
});
}
});
При быстром вводе пользователь может инициировать несколько запросов подряд. Без контроля возможна ситуация, когда старый ответ перезапишет новый результат.
Использование AbortController устраняет проблему:
let controller = null;
load: function(query, callback) {
const key = normalizeQuery(query);
const cached = getCache(key);
if (cached) {
callback(cached);
return;
}
if (controller) {
controller.abort();
}
controller = new AbortController();
fetch(`/api/search?q=${query}`, {
signal: controller.signal
})
.then(r => r.json())
.then(data => {
setCache(key, data);
callback(data);
})
.catch(err => {
if (err.name !== "AbortError") {
callback();
}
});
}
В зависимости от архитектуры приложения кеш может быть:
Локальным для компонента
new TomSelect("#select", {
load: function(query, callback) {
// cache внутри замыкания
}
});
Глобальным для всего приложения
window.tsCache = window.tsCache || new Map();
Глобальный кеш эффективен при использовании нескольких селектов с одинаковыми источниками данных, но требует аккуратного управления жизненным циклом.
Для сохранения кеша между перезагрузками страницы возможно
использование localStorage.
function saveCacheToStorage(cache) {
localStorage.setItem("ts_cache", JSON.stringify([...cache]));
}
function loadCacheFromStorage() {
const raw = localStorage.getItem("ts_cache");
return raw ? new Map(JSON.parse(raw)) : new Map();
}
Ограничения такого подхода:
Иногда кешируется не только ответ API, но и результат его обработки перед передачей в Tom Select.
function transform(data) {
return data.map(item => ({
id: item.id,
title: item.name
}));
}
Кеширование после трансформации снижает повторные вычисления:
setCache(key, transform(data));
При изменении параметров селекта (например, фильтров) кеш должен учитывать контекст:
function makeKey(query, filters) {
return `${query}::${JSON.stringify(filters)}`;
}
Иначе возможна подмена результатов при одинаковом запросе в разных состояниях UI.
Эффективная стратегия кеширования в Tom Select обычно комбинирует несколько механизмов:
Такой слой превращает load из простого HTTP-вызова в
управляемую систему получения данных с контролем состояния и
производительности.