HERE Technologies предоставляет широкий набор геосервисов, которые в JavaScript-приложениях обычно используются через HERE Maps API JavaScript. При интенсивной работе с геокодированием, маршрутизацией, тайлами и поиском объектов ключевым фактором производительности становится корректная организация кэширования запросов.
Кэширование в контексте картографических API — это не только снижение количества сетевых вызовов, но и стабилизация UX, уменьшение задержек, контроль расходов и соблюдение лимитов API.
Геокодирование (address → coordinates) и обратное геокодирование (coordinates → address) — одни из самых дорогих и часто вызываемых операций.
Особенности:
Пример запроса:
platform.getSearchService().geocode({
q: 'Almaty, Abay Street 10'
}, result => {
console.log(result);
});
Запросы маршрутов (routing) менее повторяемы, но часто имеют локальные повторения:
Особенность маршрутов — высокая чувствительность к параметрам:
Тайлы (map tiles) — наиболее кэшируемый слой:
Поиск объектов:
Запросы часто повторяются с минимальными отличиями.
Главная проблема кэширования геозапросов — вариативность входных данных.
Пример проблемы:
"Almaty, Abay 10""Abay street 10, Almaty"" almaty abay 10"Решение — нормализация:
function normalizeQuery(query) {
return query
.trim()
.toLowerCase()
.replace(/\s+/g, ' ');
}
Для разных типов запросов ключ должен включать:
Пример:
function buildCacheKey(type, params) {
return `${type}:${JSON.stringify(params)}`;
}
Более строгий вариант:
function buildCacheKey(type, params) {
return type + ':' + btoa(unescape(encodeURIComponent(
JSON.stringify(params)
)));
}
Геоданные имеют разную «живучесть»:
Пример структуры:
const cacheEntry = {
data: result,
timestamp: Date.now(),
ttl: 1000 * 60 * 60 // 1 час
};
Самый простой слой — объект Map:
class MemoryCache {
constructor() {
this.store = new Map();
}
set(key, value, ttl = 60000) {
this.store.set(key, {
value,
expiry: Date.now() + ttl
});
}
get(key) {
const entry = this.store.get(key);
if (!entry) return null;
if (Date.now() > entry.expiry) {
this.store.delete(key);
return null;
}
return entry.value;
}
}
Применение:
const cache = new MemoryCache();
function cachedGeocode(params, callback) {
const key = buildCacheKey('geocode', params);
const cached = cache.get(key);
if (cached) {
callback(cached);
return;
}
platform.getSearchService().geocode(params, result => {
cache.set(key, result, 3600000);
callback(result);
});
}
Подходит для:
Ограничения:
function setLocalCache(key, data, ttl) {
const payload = {
data,
expiry: Date.now() + ttl
};
localStorage.setItem(key, JSON.stringify(payload));
}
function getLocalCache(key) {
const raw = localStorage.getItem(key);
if (!raw) return null;
const parsed = JSON.parse(raw);
if (Date.now() > parsed.expiry) {
localStorage.removeItem(key);
return null;
}
return parsed.data;
}
IndexedDB предпочтительнее для:
Структура:
geocode, routes,
searchПсевдологика:
const record = {
key: 'geocode:almaty_10',
value: {...},
expiry: Date.now() + 86400000
};
HERE Maps API JavaScript использует HTTP-запросы к backend-сервисам, что позволяет применять:
Позволяет избегать повторной загрузки идентичных данных.
Service Worker особенно эффективен для:
Пример стратегии:
self.addEventListener('fetch', event => {
if (event.request.url.includes('tile')) {
event.respondWith(
caches.open('tiles-cache').then(cache => {
return cache.match(event.request).then(response => {
return response || fetch(event.request).then(fetchRes => {
cache.put(event.request, fetchRes.clone());
return fetchRes;
});
});
})
);
}
});
Если несколько компонентов одновременно запрашивают одинаковые данные:
const pendingRequests = new Map();
function dedupedRequest(key, fn) {
if (pendingRequests.has(key)) {
return pendingRequests.get(key);
}
const promise = fn().finally(() => {
pendingRequests.delete(key);
});
pendingRequests.set(key, promise);
return promise;
}
Использование:
function geocode(params) {
const key = buildCacheKey('geocode', params);
return dedupedRequest(key, () =>
platform.getSearchService().geocode(params)
);
}
Подход, при котором:
async function swr(key, fetchFn, cache) {
const cached = cache.get(key);
if (cached) {
fetchFn().then(newData => {
cache.set(key, newData, 3600000);
});
return cached;
}
const fresh = await fetchFn();
cache.set(key, fresh, 3600000);
return fresh;
}
Маршруты чувствительны к:
Поэтому кэширование требует сегментации:
function routeCacheKey(params) {
return [
params.origin,
params.destination,
params.mode,
new Date().toISOString().slice(0, 13) // часовой слот
].join('|');
}
Основные стратегии:
Практически применимая схема:
Иерархия:
При росте нагрузки критично:
LRU-пример:
class LRUCache {
constructor(limit = 100) {
this.limit = limit;
this.map = new Map();
}
get(key) {
if (!this.map.has(key)) return null;
const value = this.map.get(key);
this.map.delete(key);
this.map.set(key, value);
return value;
}
set(key, value) {
if (this.map.size >= this.limit) {
const firstKey = this.map.keys().next().value;
this.map.delete(firstKey);
}
this.map.set(key, value);
}
}