Debouncing и Throttling

12.02.2012
@LEXXX_NF

Зачастую при создании обработчиков событий требуется, чтобы они запускались не каждый раз при возникновении события, а не чаще определённого интервала. Или если событие возникает слишком часто, то обработчик должен сработать только один раз — для последнего события. Чтобы наделить обработчики такими возможностями используется специальная функция-декоратор.

Чтобы было понятнее, приведу пару примеров из практики web-разработки.

Задача

Допустим нам нужно сделать автоподсказку на сайте для поля поиска как у Яндекса. Запрашивать данные после каждого введённого символа — слишком накладно. Делать запросы с определённым интервалом времени — лучше, но не эффективно, если в поле ничего не вводится. Было бы хорошо запрашивать данные при нажатии клавиши, но не чаще, чем раз в секунду. Такая логика работы попадает под паттерн Throttling.

Throttle переводится как «сжимать», «сдавливать». Этот декоратор «притормаживает» функцию, позволяя задать минимальный интервал времени между срабатываниями. Если функция вызывается тогда, когда указанный интервал еще не истёк, то такой вызов игнорируется.

Для второго примера представим, что у нас есть форма с полем email. Нужно проверять, корректно ли введено значение. На какое событие лучше повесить проверку? Можно на blur, но, если пользователь не уберёт курсор с поля ввода, оно не сработает. Можно на keypress, но тогда пользователь получит ошибку уже после первого введённого символа. Было бы хорошо делать проверку только тогда, когда пользователь закончил вводить данные. Реализовать этот функционал поможет паттерн Debouncing.

Термин Debounce, по-видимому, пришел к нам из микроэлектроники, где он обозначает устранение дребезжания сигнала. Этот декоратор игнорирует все вызовы функции пока с момента возникновения последнего события не пройдёт заданного времени. Таким образом, от Throttling’га он отличается тем, что функция вызовется всего один раз.

Решение

Я уже было хотел сесть писать код, но вовремя успел остановить себя. Немного — хоте нет, много — погуглив, я нашел прекрасную реализацию обоих паттернов, которая называется jQuery Throttle. Вот ссылка: https://github.com/mekwall/jquery-throttle. Несмотря на то, что в названии фигурирует jQuery, это решение прекрасно работает и с ним и без него.

jQuery Throttle принимает в качестве аргумента нашу функцию и возвращает новую, наделённую нужными нам свойствами Throttling’га или Debouncing’га, которую мы можем смело навешивать на событие.

Если есть jQuery, то синтаксис такой.

var fn = $.throttle( fn, [timeout], [callback], [delayed], [trailing] );

и

var fn = $.debounce( fn, [timeout], [callback], [delayed], [trailing] );

Без jQuery $ заменяется на me.

var fn = me.throttle( fn, [timeout], [callback], [delayed], [trailing] );

и

var fn = me.debounce( fn, [timeout], [callback], [delayed], [trailing] );

Аргументы

fn исходная функция
timeout задержка
callback сюда можно передать еще одну функцию, но я не понял, зачем она нужная
delayed если true, то функция не будет вызвана сразу при первом возникновении события
trailing если true, то функция будет вызвана еще раз в самом конце цепочки вызовов

Я не смог по достоинству оценить 3 последних параметра, но, уверен, они кому-нибудь пригодятся. Зато этом решении корректно работает this.

Обычное для jQuery назначения обработчика для события будет выглядеть так:

$( 'input[name=”email”]' ).keypress( $.debounce( checkEmail, 1000, null, true ) );

Небольшой пример: demo.

Еще по теме

Еще почитать про Debouncing и Throttling можно тут.

У себя в блоге Джон Резиг (создатель jQuery) написал статью о трудностях, с которыми столкнулся Twitter, когда во время перехода на страницы с бесконечной прокруткой: Learning from Twitter.

#1
Са П.
13.08.2014 16:43
Я тоже как-то написал функции, потом оказалось, что это debounce )) http://plutov.by/post/fn_delay

Писáть здесь