Как реализовать кэш с методами get, set, delete и clear с поддержкой TTL в JavaScript
Что проверяют
Умеете ли вы довести простой класс до надёжного: что с просроченными записями, которые никто не читает, что с таймерами и граничными значениями TTL. Обёртку над Map пишут все, смотрят на детали. Следом просят ограничить размер кеша или научить его хранить промисы.
Как отвечать
Каркас на Map, в значении объект { value, expiresAt }:
set(key, value, ttl = this.defaultTtl):expiresAt = Date.now() + ttl. TTL проверяется на входе:undefinedдаётNaN, сравнение сNaNвсегда ложно, и запись молча становится вечной. Для «без срока» используйте явныйInfinity;get: если записи нет илиDate.now() >= expiresAt, удалить её и вернутьundefined. Лучшеundefinedплюс методhas, чемnull, иначе нельзя закешировать самnull;deleteиclear: удаляют запись и заодно её таймер, если он есть;- чистка: ленивой проверки в
getмало, непрочитанные ключи копятся. Нужен периодический обход или вытеснение приset.
Если истечение сделано через setTimeout на каждый ключ, при перезаписи и удалении старый таймер отменяется clearTimeout. В Node.js таймер держит процесс живым, поэтому ему вызывают unref(). Задержка больше 2147483647 мс (около 24,8 дня) переполняется, и таймер срабатывает почти сразу.
Ограничение размера строится на свойстве Map помнить порядок вставки. В get запись удаляется и вставляется заново, так она уходит в конец, а при переполнении вытесняется самая старая через map.keys().next().value. Получается LRU без дополнительных структур.
Сильный ход: кешировать промис запроса, а не результат. Параллельные вызовы получат один промис вместо десяти запросов, а при ошибке запись удаляется, чтобы отказ не закешировался.
Где спотыкаются
Ставят setTimeout в set и не отменяют его при повторном set. Старый таймер срабатывает и удаляет свежее значение раньше срока.
Проверяют наличие через if (cache.get(key)): значения 0, пустая строка и false выглядят как промах.
Рассуждают о «потокобезопасности» кеша в браузере. Основной поток JavaScript один, реальные гонки здесь в асинхронности: await между проверкой и записью.