Как реализовать функцию с кешированием результата и обновлением при протухании кеша
Что проверяют
Замыкания, работу с промисами и понимание проблем кеша: ключ, время жизни, одновременные запросы. Следом обычно спрашивают, что будет, если функцию вызвали пять раз подряд, пока первый запрос ещё идёт, и чем самописное решение уступает готовым библиотекам.
Как отвечать
Оформите обёртку withCache(fn, { ttl }), которая возвращает новую функцию, а внутри держит Map из ключа в запись { promise, expiresAt }:
- ключ: кеш по аргументам (URL и параметры запроса), а не одна глобальная ячейка. Иначе второй адрес получит данные первого;
- храним промис: запись кладётся в кеш сразу при старте запроса. Параллельные вызовы получают тот же промис, и сервер видит один запрос вместо пяти;
- ошибки: при отклонении запись удаляется, иначе ошибка закешируется на весь срок жизни. Для
fetchпроверяйтеresponse.ok: ответ500промис не отклоняет; - протухание:
expiresAtсчитается от момента получения ответа и сравнивается при чтении. Для измерения интервалов лучшеperformance.now(): он монотонный и не прыгает при переводе системных часов, в отличие отDate.now(); - stale-while-revalidate: протухшее значение сразу отдаётся, а свежее грузится в фоне, интерфейс не ждёт;
- размер: лимит записей с вытеснением старых.
Mapхранит порядок вставки, на этом строится простой LRU.
Покажите, что знаете, как это устроено в готовых инструментах. HTTP-заголовок Cache-Control: max-age=60, stale-while-revalidate=300 решает ту же задачу на уровне браузера. В TanStack Query за это отвечают staleTime и gcTime, в SWR дедупликация параллельных запросов встроена.
Где спотыкаются
Делают один глобальный объект кеша без ключа. Пока URL один, всё работает, а второй вызов с другим адресом до истечения срока получает чужие данные.
Кладут в кеш готовый результат, а не промис. Пока первый запрос в полёте или сразу после протухания, каждый вызов уходит в сеть отдельно, и десяток компонентов на странице даёт десяток одинаковых запросов.
Говорят про гонки потоков и мьютексы. JavaScript в браузере выполняет этот код в одном потоке, гонка здесь между асинхронными вызовами, и решается она хранением промиса, а не блокировками.