Мемоизация в HMPL. DevBlog №1

В версии 2.1.3, помимо прочего, был введён новый функционал для улучшения производительности сайтов, использующих hmpl.js.

Мемоизация запроса — это один из отличнейших способов оптимизации в программировании. «Что это и как оно работает?» — на эти вопросы я постараюсь ответить в данной статье.

Кстати, все нововведения, связанные с языком шаблонов, вы можете найти в тематическом тг-канале.

Понятие мемоизации

Прежде чем перейти к рассмотрению конкретного функционала, для начала рассмотрим данное понятия в программировании вообще.

Мемоизация — сохранение результатов выполнения функций для предотвращения повторных вычислений. Это один из способов оптимизации, применяемый для увеличения скорости выполнения компьютерных программ. 

Перед вызовом функции проверяется, вызывалась ли функция ранее:

  • если не вызывалась, то функция вызывается, и результат её выполнения сохраняется;

  • если вызывалась, то используется сохранённый результат.

Примером мемоизации в JavaScript может быть следующий код:

// До внедрения мемоизации

const reverseText = (string) =>{
  const len = string.length;
  let reversedText = new Array();
  let j = 0;
  for (let i = len - 1; i >= 0; i--){
    reversedText[j] = string[i];
    j++;
  }
  return reversedText.join('');
}

const a = reverseText("a123"); // Срабатывает цикл
const b = reverseText("a123"); // Срабатывает цикл

// После внедрения мемоизации

const memoizeFn = (fn) => {
  const cache = {};
  return (string) => {
    if (string in cache) return cache[string];
    return (cache[string] = fn(string));
  };
};

const memoizedReverseText = memoizeFn(reverseText);

const a = memoizedReverseText("a123"); // Срабатывает цикл
const b = memoizedReverseText("a123"); // Не срабатывает цикл

Мы создаём обёртку (функция высшего порядка) над уже существующий функцией, которая добавляет некое состояние, обозначенное cache. В кэше хранятся аргументы и, соответственно, значения, получаемые из функции. По ключу, если он равен входному аргументу, в объекте уже можно получить готовый результат, не переворачивая заново строку.

Это и есть та основа на которой строится вся оптимизация. Банально, мы не повторяем код заново, а лишь берём уже вычисленное значение.

Также, объект был назван кэшем неспроста. Кэш— это промежуточный буфер с быстрым доступом к нему, содержащий информацию, которая может быть запрошена с наибольшей вероятностью.

Тут уже можно вспомнить и память, которая располагается между процессором и оперативкой, но это уже явно немного другая история.

В целом, мемоизация повсеместно применяется в ПО, что делает её отличным способом по быстрому ускорить то или иное выполнение кода. Но у данного способа есть конечно и минусы.

Основным минусом, конечно, является лишнее выделение памяти на хранение результатов. Если функция выполняется один раз, то смысла мемоизировать её возвращаемые значения попросту нет.

Мемоизация в HMPL

Так как HMPL — это язык шаблонов для отображения пользовательского интерфейса с сервера на клиенте, то мемоизировать нужно будет http запросы. Соответственно, предполагаемым результатом будет сохранение HTML разметки. Вот пример того, как работает HMPL:

const newDiv = compile(
  `
{{ src: "/api/getTheSquareRootOf256", after: "click:button" }}
` )().response;

По слушателю событий на кнопке очевидно, что запрос на сервер может отправляться сколь угодно раз, но вот квадратный корень из 256 как был 16, так и будет, поэтому запрос, теоретически, будет приходить один и тот же.

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

Специально для оптимизации вот этого процесса было введено дополнительное поле, которое имеет название memo.

const newDiv = compile(
  `
{{ src: "/api/getTheSquareRootOf256", memo:true, after: "click:button" }}
` )().response;

Значением оно принимает true или false. Работает только для объектов запросов, которые теоретически неоднократно отправляются.

Для наглядного отображения процесса была создана небольшая диаграмма:

1. Мемоизация в HMPL (источник)

Тут тоже фигурирует понятие кэша, которое уже было использовано ранее. Также, если мы берём HTTP запросы, то рассматриваются дополнительные понятия fresh и stale response, ревалидация и т.д. Это всё идёт из теории HTTP кэша. Более подробно этот момент рассматривается в mdn документации тут. Можно провести аналогию, что мемоизация в HMPL по логике сравнима с no-cache значением режима кэширования.

Пример работы hmpl без мемоизации и с ней в DOM:

Без мемоизации:

9e4cdad05aa6720c8df1b749887e4ecb.gif

С мемоизацией:

3c1222e2240e92c6528e2d9e71410005.gif

В ходе теста активно нажималась кнопка получения пользовательского интерфейса с сервера.

В одном случае, мы постоянно заменяем div на новый, хотя ответ от сервера приходит один и тот же, в другом же, мы сохраняем этот же элемент, но только если ответы одинаковы.

Мемоизация для типов файлов с расширением HMPL

Также, мемоизация будет доступна не только для одного объекта запроса, но и для всех объектов, полученных из функции compile с включённой опцией memo:

const templateFn = hmpl.compile(
  `{ 
     {
       "src":"/api/test" 
     } 
   }`,
  {
    memo: true,
  }
);

const elementObj1 = templateFn();
const elementObj2 = templateFn();

Она никак не помешает другим объектам запроса, которые срабатывают только один раз.

Так как на функции compile основана работа hmpl-loader, то в скором времени будет добавлена опция, при которой можно будет включить мемоизацию для всех файлов с расширением .hmpl:

module.exports = {
  module: {
    rules: [
      {
        test: /\.hmpl$/i,
        use: [{ loader: "hmpl-loader", options: { memo: true } }],
      },
    ],
  },
};

Для этого уже открыт issue.

Всем большое спасибо за прочтение данной статьи!

P.S. Больше изменений, которые были внесены в новую версию 2.1.3, можно найти тут.

© Habrahabr.ru