Функции-декораторы (обёртки)
Функции-декораторы на примере поиска по товарам в интернет-магазине
Декоратор — такой приём при котором поведение изначальной функции (или класса) расширяется / изменяется / дополняется без изменения способа его вызова. Декоратор может "прокачать" изначальную функцию новым функционалом и при этом логика использования изначальной функции никак не изменится.
Сразу пример. Мы разрабатываем сайт интернет-магазина. У нас есть функция, которая берёт текст юзера из поля поиска и ходит с ним на сервер, чтобы узнать, есть ли товары с таким названием.
<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...но так делать не стоит:
- хорошая функция должна решать какую-то одну задачу
- скорее всего придётся переиспользовать логику ограничений выше в других местах.
Пишем первый декоратор
Напишем декоратор, который не даст слишком часто отправлять запросы на сервер. Логика его работы следующая:
- Выставляем таймер отработки функции
- Запрашиваем вызов функции
- Ждём пока время таймера пройдёт и только потом выполняем функцию
- Если функция не успела выполниться и была вызвана повторно, таймер обновлятся.
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);
})
Демка с итоговым вариантом
Итог
Декорирование — удобный приём, который позволяет навесить на функцию дополнительный функционал или скорректировать её работу. Можно добавить логгирование, отправку целей аналитики, любые проверки о которых исходная функция знать не будет.
Для функций-декораторов работают те же правила хорошего тона, что и для обычных функций: один декоратор должен добавлять какую-то одну фичу. Таким образом его будет проще использовать, тестировать и переиспользовать.
Комментарии