Программная инженерия

Реализация отката в Javascript

Что такое дебаунс и как его реализовать

Если вы занимаетесь мобильной разработкой, возможно, вы уже знаете, что такое debounce.

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

Термин «устранение дребезга» впервые появился в электронной и аппаратной промышленности.

Механический отскок

Отскок — это свойство механических кнопок и переключателей, которые при нажатии создают электрический шум. Этот шум затеняет входной сигнал, из-за чего система неправильно его считывает.

Если вы когда-либо работали с микроконтроллерами и тому подобным, вы бы хорошо разобрались в этом вопросе.

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

В Geek Pub есть отличная статья на эту тему. Если вас больше интересует устранение дребезга в микроконтроллерах, обязательно ознакомьтесь с ним.

Отказ программного обеспечения

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

Однако эта история будет посвящена входному шуму в веб-приложениях, серверных и мобильных приложениях,и тому, как это влияет на производительность.

Где происходит дребезг входного сигнала

Наиболее распространенные места, где вы можете столкнуться с этим:

  • Поля ввода пользователя
  • Кнопки и переключатели
  • Чтение очереди сообщений

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

В нашем случае сам сигнал будет создаваться пользователем, например, в поле ввода, которое имеет прослушиватель изменений.

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

Рассмотрим следующий пример:

# HTML
<input onKeyUp="inputChanged()" type="text"/>
# JS
function inputChanged() {
    console.log("Input event detected.");
}

Это один из обычных случаев, когда вы хотите прослушать изменения пользовательского ввода.

Обратите внимание, что onChange не используется, так как событие генерируется только тогда, когда focus смещается из поля ввода.

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

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

Проблема здесь в том, что ввод чего-либо в поле onKeyUp скорее приведет к большому количеству событий за короткий период времени.

Если вы фильтруете некоторые результаты, пользовательский интерфейс может заикаться, и это повлияет на вашу производительность.

Возможно, вам необходимо реализовать нечеткий поиск, и поиск должен происходить в режиме реального времени, не дожидаясь, пока пользователь нажмет «Отправить». Вам нужно отправлять HTTP-запросы, чтобы получить результаты, и отправка такого количества запросов явно не выгодна.

Чтобы подробнее объяснить суть проблемы, давайте ненадолго вернемся к очереди сообщений. Например, вы можете настроить потребительский клиент Kafka с желаемым размером пакета и периодом времени, в течение которого вы хотите получать каждый пакет.

# Sample pseudo consumer
var consumer = kafka.client()
    .batch(100)
    .in(1000ms);
consumer.poll();

Приведенный выше псевдокод настроит клиент Kafka на получение сообщений либо каждую секунду, либо как только он получит 100 событий. Вы поняли идею.

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

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

Решение

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

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

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

Последнее событие в потоке, естественно, также будет использовано, только в этом случае само по себе, и еще раз, как только функция будет запущена по истечении еще 1000 мс.

Проще говоря, мы пытаемся предвидеть последнее событие в потоке. Пороговое время должно быть изменено в зависимости от обстоятельств. Если бы мы хотели реагировать более своевременно, мы бы настроили меньший временной порог. Если мы хотим, чтобы какая-то логика срабатывала только после того, как мы действительно получили «почти последнее» событие, мы бы установили для него более высокое значение.

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

В Javascript есть много библиотек, особенно в разных фреймворках.

Простой способ реализовать метод устранения дребезга — использовать setTimeout, как показано в следующем фрагменте кода:

# HTML
<input id="input-field" onKeyUp="inputChanged()" type="text"/>
# JS
const inputField = document.getElementById("input-field");
function getResult() {
   console.log("Input event detected.");
   console.log(inputField.value);
   // Do something with the input
}
const debounce = {
   isWaiting: false,
   
   submit: function (func) {
      if (!this.isWaiting) {
        this.isWaiting = true;
         
         setTimeout(() => {
             func.apply();
             this.isWaiting = false;
         }, 1000);
      }
   }
}
function inputChanged() {
 debounce.submit(getResult);
}

Объект debounce в приведенном выше фрагменте просто позволит нам выполнять submit задач, но при этом будет определять, ожидает ли отправка выполнения, и если да, просто пропустить отправку другой задачи.

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

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

Ванильный подход Javascript выглядит следующим образом:

# HTML
<input id="input-field" onKeyUp="inputChanged()" type="text"/>
# JS
const inputField = document.getElementById("input-field");
function getResult() {
   console.log("Input event detected.");
   console.log(inputField.value);
   // Do something with the input
}
const debounce = {
   timerId: 0,
   timeout: 1000,
   
   submit: function (func) {
       this.cancel();
       
       this.timerId = setTimeout(() => {
           func.apply(this);
       }, this.timeout);
   },
   
   cancel: function() {
       clearTimeout(this.timerId);
   }
}
function inputChanged() {
 debounce.submit(getResult);
}

Ключевые моменты

Анализируя приведенный выше фрагмент, ключевыми моментами являются:

  • Отправка задачи устанавливает таймер с пороговым временем
  • Каждое последующее событие отменяет таймер.
  • Таким образом, продлевая исполнение, т.е. задержка чтения событий
  • Задача выполняется только по истечении порогового времени
  • Т.е. это не было продлено другим событием

Приведенный выше фрагмент повторяет то, что было сказано ранее и показано на диаграмме Поток событий.

Я настоятельно рекомендую прочитать больше о debounce. В Интернете есть много сообщений, объясняющих проблему более подробно, при различных обстоятельствах и отображающих различные варианты использования.

Трей Хаффин очень хорошо объясняет эту проблему, предлагая аккуратный подход к ее решению в своей История о Debounce in Javascript Medium. Проверьте это.

Если вы являетесь поклонником lodash, возможно, вы захотите ознакомиться с их документами, так как в них реализован метод debounce для использования.

У RxJS, очевидно, тоже есть реализация, но даже если вы лично ее не используете, обязательно ознакомьтесь с документацией, так как она очень хорошо иллюстрирует концепцию. Следующая документация по RxJS вдохновила меня на написание иллюстрированной диаграммы — Потока событий.

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

Если вы хотите узнать больше о debounce, если у вас есть какие-либо предложения или если вы нашли опечатку в истории и фрагментах кода, не стесняйтесь писать комментарии!

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

Спасибо за чтение! 🎉