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

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


Основы кэширования

Кэширование представляет собой сохранение результатов сетевых запросов для повторного использования. В контексте Mithril.js чаще всего речь идёт о кэшировании данных, полученных через m.request. Основные цели кэширования:

  • Снижение числа HTTP-запросов.
  • Повышение скорости отклика интерфейса.
  • Сохранение состояния данных между переходами страниц.

Простейший подход — хранение результатов в локальной переменной или объекте:

let cache = {};

function fetchUser(id) {
    if (cache[id]) return Promise.resolve(cache[id]);

    return m.request({
        method: "GET",
        url: `/api/users/${id}`
    }).then(data => {
        cache[id] = data;
        return data;
    });
}

В этом примере при повторном вызове fetchUser с тем же id сетевой запрос не выполняется, данные берутся из кэша.


Использование кэша с m.request

Mithril.js позволяет тонко настраивать запросы с помощью m.request. Для реализации кэширования можно расширить объект опций:

function cachedRequest(options, cacheKey) {
    if (cachedRequest.cache[cacheKey]) {
        return Promise.resolve(cachedRequest.cache[cacheKey]);
    }

    return m.request(options).then(result => {
        cachedRequest.cache[cacheKey] = result;
        return result;
    });
}

cachedRequest.cache = {};

Такой подход универсален: любые GET-запросы можно обернуть функцией cachedRequest, передавая уникальный cacheKey для идентификации данных.


Кэширование с таймаутом

Для динамических данных важно не хранить кэш бесконечно. Можно добавить механизм устаревания кэша:

function timedCacheRequest(options, cacheKey, ttl = 30000) {
    const now = Date.now();

    if (timedCacheRequest.cache[cacheKey] && (now - timedCacheRequest.timestamps[cacheKey]) < ttl) {
        return Promise.resolve(timedCacheRequest.cache[cacheKey]);
    }

    return m.request(options).then(result => {
        timedCacheRequest.cache[cacheKey] = result;
        timedCacheRequest.timestamps[cacheKey] = Date.now();
        return result;
    });
}

timedCacheRequest.cache = {};
timedCacheRequest.timestamps = {};

Параметр ttl (time-to-live) задаёт время жизни кэшированных данных. После его истечения запрос будет выполняться повторно, обновляя кэш.


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

POST-запросы по определению изменяют состояние сервера, поэтому кэшировать их стоит с осторожностью. Если сервер гарантирует идемпотентность (например, повторный запрос не изменяет результат), можно использовать кэширование аналогично GET:

let postCache = {};

function cachedPostRequest(url, body) {
    const key = JSON.stringify({ url, body });

    if (postCache[key]) return Promise.resolve(postCache[key]);

    return m.request({
        method: "POST",
        url,
        body
    }).then(data => {
        postCache[key] = data;
        return data;
    });
}

При этом необходимо строго контролировать уникальность key, чтобы не перепутать разные запросы с одинаковым URL.


Интеграция с компонентами Mithril

Кэширование тесно связано с жизненным циклом компонентов. Например, для отображения списка пользователей:

const UserList = {
    users: [],
    oninit() {
        timedCacheRequest({ method: "GET", url: "/api/users" }, "usersList").then(data => {
            this.users = data;
        });
    },
    view() {
        return m("ul",
            this.users.map(user => m("li", user.name))
        );
    }
};

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


Продвинутые стратегии кэширования

  1. Кэш с инвалидацией по событию — данные обновляются только при определённых действиях пользователя (например, после добавления нового элемента).
  2. Кэширование с глубокой структурой — сохранение сложных объектов с вложенными сущностями, чтобы минимизировать количество сетевых обращений.
  3. Комбинированное кэширование — сочетание локального кэша и localStorage или IndexedDB для долгосрочного хранения данных.

Пример кэширования с localStorage:

function persistentCacheRequest(options, cacheKey) {
    const cached = localStorage.getItem(cacheKey);
    if (cached) return Promise.resolve(JSON.parse(cached));

    return m.request(options).then(result => {
        localStorage.setItem(cacheKey, JSON.stringify(result));
        return result;
    });
}

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


Рекомендации

  • Кэшировать только GET-запросы или идемпотентные POST-запросы.
  • Обязательно реализовать механизм инвалидации кэша.
  • Учитывать размер данных при хранении в памяти или localStorage.
  • Для сложных приложений рассмотреть использование специализированных библиотек управления состоянием (например, Redux, MobX) совместно с кэшированием запросов.

Кэширование запросов в Mithril.js повышает производительность приложений, снижает нагрузку на сервер и делает интерфейс отзывчивым. Встроенные возможности m.request позволяют гибко управлять кэшем, обеспечивая быстрый и предсказуемый доступ к данным.