ilyachalov (ilyachalov) wrote,
ilyachalov
ilyachalov

Categories:

Учебник по JavaScript: ч.1: декоратор debounce

Начало тут: Учебник по JavaScript: ч.1: new Function, планирование, декораторы.

Понравилась задача «Декоратор debounce» к подразделу 6.9 «Декораторы и переадресация вызова, call/apply» учебника.

Требуется написать функцию-декоратор debounce(f, ms), которая гарантирует, что заданная функция f может быть запущена не чаще одного раза за ms миллисекунд (более частые вызовы должны игнорироваться).

«Яндекс.Переводчик» и «Google.Переводчик» не справляются со словом «debounce». По-русски дословно оно означает «устранение дребезга». Как я понимаю, это слово пошло из электротехники. При переключении механических ключей из положения «включено» в положение «выключено» и наоборот существует проблема дребезга контактов (напряжение электрического тока после переключения не устанавливается сразу в нужное значение, а некоторое время колеблется возле правильного значения; колебания постепенно угасают). Люди придумали несколько методов устранения этого нежелательного явления («устранение дребезга», то есть «debounce»). Со временем это словечко перекочевало в программирование, в котором так стали называть запрет запуска определенной функции чаще, чем это необходимо (тоже своеобразное «устранение дребезга»). Почитать об этом можно в англоязычной википедии:

https://en.wikipedia.org/wiki/Switch#Contact_bounce

Для чего можно применить «debounce»? В тексте задачи приводится пример: существуют функции для получения/обновления данных. Получение/обновление данных не требуется выполнять слишком часто (через слишком маленькие промежутки времени), поэтому можно запретить слишком частый вызов таких функций и таким образом уменьшить нагрузку на процессор.

В тексте задачи для тестирования будущей функции-декоратора debounce(f, ms) дан следующий код:
let f = debounce(alert, 1000);

f(1); // выполнить немедленно
f(2); // проигнорировать

setTimeout( () => f(3), 100);  // проигнорировать (прошло только 100 мс)
setTimeout( () => f(4), 1100); // выполнить
setTimeout( () => f(5), 1500); // проигнорировать (прошло только 400 мс от последнего вызова)

Декоратор debounce получил на вход функцию alert, а на выходе вернул функцию f, это тот же alert, только с новой функциональностью: функция f не может быть запущена чаще, чем один раз в 1000 мс (1 секунда). Из-за этого из пяти вызовов функции f в тестовом коде сработают только два: f(1) и f(4), в результате чего на экран будут последовательно выведены два сообщения: одно со значением 1 и другое — со значением 4.

Не слишком много времени у меня заняло написание такого варианта декоратора debounce:

function debounce(f, ms) {
    let start;

    function wrapper(...args) {

        if (start === undefined) {
            start = Date.now();
            f.apply(this, args);
        } else {
            let elapsedTime = Date.now() - start;
            if (elapsedTime >= ms) {
                start = Date.now();
                f.apply(this, args);
            }
        }

    }

    return wrapper;
}

В переменной start хранится время последнего запуска функции f, то есть это начало интервала между разными запусками функции f. При первом запуске функции f эта переменная не определена (start === undefined), мы немедленно запускаем функцию f, как требуется по условиям задачи. При следующих запусках функции f определяем прошедшее время elapsedTime и, если оно больше или равно заданному интервалу ms миллисекунд, то запускаем функцию f, иначе — игнорируем ее и не запускаем.

Этот вариант кода технически работает и даже проходит тесты авторов учебника из песочницы. Однако, приведенный в начале поста тестовый код работает не так, как планировалось. У меня запускаются все пять вызовов функции f. В чем же дело?

А дело в том, что я измеряю заданный интервал в 1000 миллисекунд от начала запуска функции f. То есть время работы функции f в моем варианте кода входит в этот интервал. А время работы функции f (на самом деле, у нас это та же функция alert, как уже было сказано ранее) включает время отображения сообщения на экране, затем время, в течение которого я двигаю мышь к кнопке «ОК» отобразившегося сообщения, и время, в течение которого я нажимаю на эту кнопку «ОК». А так как я не профессиональный кибератлет, то все эти телодвижения у меня в большинстве случаев занимают больше одной секунды. Таким образом, в случае заданного интервала в 1000 миллисекунд мой вариант кода запускает все пять вызовов функции f в тестовом коде.

Для исправления ситуации нужно всего лишь переставить определение начала интервала на момент после окончания работы функции f:

function debounce(f, ms) {
    let start;

    function wrapper(...args) {

        if (start === undefined) {
            f.apply(this, args);
            start = Date.now();           // переставил
        } else {
            let elapsedTime = Date.now() - start;
            if (elapsedTime >= ms) {
                f.apply(this, args);
                start = Date.now();       // переставил
            }
        }

    }

    return wrapper;
}

Этот вариант кода декоратора debounce отрабатывает на тестовом коде из начала поста так, как планировалось: запускаются только вызовы функции f с параметром 1 и с параметром 4.

Вариант кода от авторов учебника, впрочем, мне понравился больше. Он проще и изящнее:
function debounce(f, ms) {

    let isCooldown = false;

    return function() {
        if (isCooldown) return;

        f.apply(this, arguments);

        isCooldown = true;

        setTimeout(() => isCooldown = false, ms);
    };

}

Тут ничего не замеряется. Регулировка запусков функции f выполняется с помощью переменной isCooldown (слово «Cooldown» в данном случае можно перевести на русский как «передышка»). Пока эта переменная равна false (в данный момент нет передышки, как поётся в известной песне, «stand up and fight!»), можно запускать функцию f. Как только эта переменная принимает значение true (начинается передышка), запуски функции f игнорируются. Смена значения переменной isCooldown планируется с помощью функции setTimeout через ms миллисекунд.
Tags: Английский язык, Образование, Программирование
Subscribe

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments