Throttling и debouncing

Throttling и debouncing — паттерны ограничения частоты вызовов функций. В контексте timeago.js они применяются для управления частотой обновлений и реакции на события (scroll, resize, WebSocket).


Когда нужны throttle и debounce

  • Обновление временных меток при получении данных через WebSocket (поток событий).
  • Инициализация после прокрутки страницы.
  • Перезапуск render при изменении локали.
  • Обновление при пакетном добавлении элементов в DOM.

Debounce: задержать до паузы

Debounce откладывает вызов до тех пор, пока события не прекратятся на N миллисекунд. Полезно для ситуаций, когда данные приходят пакетами.

function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

Применение при получении данных через WebSocket:

import { render, cancel } from 'timeago.js';

const refreshTimeElements = debounce(() => {
  const nodes = document.querySelectorAll('[datetime]');
  cancel(nodes);
  render(nodes, 'ru');
}, 300);

socket.onmess age = () => {
  // ... добавить элемент в DOM ...
  refreshTimeElements(); // один вызов после серии сообщений
};

Throttle: не чаще N раз в секунду

Throttle ограничивает частоту вызовов независимо от интенсивности событий.

function throttle(fn, interval) {
  let lastCall = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastCall >= interval) {
      lastCall = now;
      return fn.apply(this, args);
    }
  };
}

Применение при прокрутке:

import { render, cancel } from 'timeago.js';

const onScr oll = throttle(() => {
  const visibleNodes = Array.from(
    document.querySelectorAll('[datetime]')
  ).filter(el => {
    const rect = el.getBoundingClientRect();
    return rect.top < window.innerHeight && rect.bottom > 0;
  });

  cancel(document.querySelectorAll('[datetime]'));
  render(visibleNodes, 'ru');
}, 500);

window.addEventListener('scroll', onScroll, { passive: true });

Debounce для смены локали

При динамическом переключении языка пользователь может быстро менять локаль:

import { cancel, render } from 'timeago.js';

const applyLocale = debounce((locale) => {
  const nodes = document.querySelectorAll('[datetime]');
  cancel(nodes);
  render(nodes, locale);
}, 200);

localeSelector.addEventListener('change', (e) => {
  applyLocale(e.target.value);
});

Throttle для обновления при фокусе

При возвращении на вкладку не нужно обновлять чаще раза в 5 секунд:

import { cancel, render } from 'timeago.js';

const refreshOnFocus = throttle(() => {
  const nodes = document.querySelectorAll('[datetime]');
  cancel(nodes);
  render(nodes, 'ru');
}, 5000);

document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'visible') {
    refreshOnFocus();
  }
});

Debounce при пакетном добавлении элементов

MutationObserver вызывается очень часто — debounce снижает нагрузку:

import { render } from 'timeago.js';

const pendingElements = new Set();

const flushPending = debounce(() => {
  if (pendingElements.size > 0) {
    render(Array.from(pendingElements), 'ru');
    pendingElements.clear();
  }
}, 100);

const mo = new MutationObserver((mutations) => {
  mutations.forEach(mutation => {
    mutation.addedNodes.forEach(node => {
      if (node.nodeType !== Node.ELEMENT_NODE) return;
      node.querySelectorAll('[datetime]').forEach(el => pendingElements.add(el));
    });
  });

  flushPending();
});

mo.observe(document.body, { childList: true, subtree: true });

Готовые реализации через lodash

import { debounce, throttle } from 'lodash-es';
import { render, cancel } from 'timeago.js';

const debouncedRefresh = debounce((locale) => {
  const nodes = document.querySelectorAll('[datetime]');
  cancel(nodes);
  render(nodes, locale);
}, 300);

const throttledVisibilityRefresh = throttle(() => {
  const nodes = document.querySelectorAll('[datetime]');
  cancel(nodes);
  render(nodes, 'ru');
}, 1000);

Отличие debounce от throttle в контексте timeago

Ситуация Подход
Пакет WebSocket-сообщений Debounce
Смена локали при наборе Debounce
Scroll-событие Throttle
Visibility change (вкладка) Throttle
Форс-обновление по кнопке Throttle
Пакетное добавление DOM-элементов Debounce