На главную

Функции-декораторы (обёртки)

Функции-декораторы на примере поиска по товарам в интернет-магазине

Декоратор — такой приём при котором поведение изначальной функции (или класса) расширяется / изменяется / дополняется без изменения способа его вызова. Декоратор может "прокачать" изначальную функцию новым функционалом и при этом логика использования изначальной функции никак не изменится.

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

    <input type="search" id="goodsSearch">

<script>
function getSearchedGoods(text) {
// в реальной жизни мы бы ходили на сервер
// и как-то обрабатывали ответ
// но тут просто выведем в консоль
console.log(`ищем товары по тексту: ${text} ...`)
}

const searchInput = document.querySelector('#goodsSearch');
let searchHandler = getSearchedGoods;

searchInput.addEventListener('input', (evt) => {
searchHandler(evt.target.value);
})
</script>

Всё круто, тикет закрыт, вы пошли пить смузи и кататься по коридору на гироскутере. Но тут приходит Вася-бэкендер и жалуется:

😡 Вы шлёте нам поисковый запрос на каждое изменение инпута пользователем. Юзер хочет найти "смартфон", и пока он это пишет, к нам летят запросы на поиск: "с", "см", "сма", "смар"... ну ты понял, не надо так! Надо отправлять запрос только после того как юзер перестал вводить.

Кхм...да, справедливо, Вася. Мы пофиксим!

И только мы собрались фиксить код, в чат пишет Гена-аналитик:

📈 Привет! Я слышал, что вы запилили новый поиск. Давай на каждый поисковый запрос с клиента, будем слать событие в нашу систему аналитики.

Штош. Мы конечно могли бы взять и добавить эту логику внутрь функции getSearchedGoods...но так делать не стоит:

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

Пишем первый декоратор

Напишем декоратор, который не даст слишком часто отправлять запросы на сервер. Логика его работы следующая:

  1. Выставляем таймер отработки функции
  2. Запрашиваем вызов функции
  3. Ждём пока время таймера пройдёт и только потом выполняем функцию
  4. Если функция не успела выполниться и была вызвана повторно, таймер обновлятся.
function withDebounce(handler, time) {
let timerId;

return function() {
// сохраняем контекст функции и аргументы
const context = this;
const args = arguments;

window.clearTimeout(timerId);

timerId = window.setTimeout(() => {
// вызываем с оригинальным контекстом и аргументами
handler.apply(context, args)
}, time);
}
}

Отлично! Давайте заиспользуем его и сократим количество запросов на сервер.

let searchHandler = getSearchedGoods;
// будем вызывать если пользователь не вводил ничего нового 1 секунду
searchHandler = withDebounce(getSearchedGoods, 1000);

searchInput.addEventListener('input', (evt) => {
searchHandler(evt.target.value);
})

Напишем второй декоратор

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

function withAnalyticsEvent(handler, eventCodeName) {
function sendAnalyticsEvent() {
// какой-то код, который отправляет событие в аналитику
// но у нас просто вывод в консоль
console.info('событие отправлено', eventCodeName)
}

return function() {
// сохраняем контекст вызова
const context = this;
handler.apply(context, arguments);
sendAnalyticsEvent();
}
}

Уже знакомым путём добавим новый декоратор.

let searchHandler = getSearchedGoods;
// Оборачиваем в функцию, отправляющую событие аналитики
searchHandler = withAnalyticsEvent(searchHandler, 'SEARCH_PRODUCT_BY_NAME');
// Оборачиваем в функцию, ограничивающую слишком частые вызовы
// считаем что юзер закончил вводить, если секунду не нажимал клавишу
searchHandler = withDebounce(searchHandler, 1000);

searchInput.addEventListener('input', (evt) => {
searchHandler(evt.target.value);
})

Демка с итоговым вариантом

Итог

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

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

Комментарии