← Назад к вопросам

Реализуйте функцию throttle

2.0 Middle🔥 121 комментариев
#Node.js и JavaScript#Кэширование и производительность

Условие

Напишите функцию throttle(fn, limit), которая гарантирует, что функция fn вызывается не чаще, чем раз в limit миллисекунд.

function throttle(fn, limit) {
  // Ваш код
}

// Пример использования:
const throttledScroll = throttle(() => console.log("scroll"), 1000);
// При частых вызовах функция будет выполняться максимум раз в секунду

Что проверяется

  • Работа с таймерами
  • Разница между throttle и debounce
  • Управление частотой вызовов функций

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение

Базовая реализация

function throttle<T extends (...args: any[]) => any>(
  fn: T,
  limit: number
): (...args: Parameters<T>) => void {
  let inThrottle: boolean = false;

  return function (...args: Parameters<T>) {
    if (!inThrottle) {
      fn(...args);
      inThrottle = true;

      setTimeout(() => {
        inThrottle = false;
      }, limit);
    }
  };
}

Как это работает

Принцип работы:

  • Флаг inThrottle блокирует повторные вызовы на время limit
  • После истечения limit миллисекунд флаг сбрасывается
  • Новые вызовы могут быть выполнены

Вызовы функции при частых обращениях:

Время:  0ms    100ms   200ms   300ms   400ms   500ms   600ms   700ms   800ms   900ms  1000ms
Вызовы: X      X       X       X       X       -       -       -       -       -      X
Выпол:  ✓      ✗       ✗       ✗       ✗       -       -       -       -       -      ✓

При throttle(fn, 300):

  • Первый вызов: ВЫПОЛНИТЬ, установить блокировку на 300мс
  • Вызовы на 100-200мс: ИГНОРИРОВАТЬ (в периоде блокировки)
  • После 300мс: блокировка снята, следующий вызов ВЫПОЛНИТСЯ

Пример использования

const throttledScroll = throttle(() => {
  console.log("Обработка scroll события");
  // Выполнится максимум раз в 1000мс
}, 1000);

window.addEventListener('scroll', throttledScroll);
// При быстрой прокрутке событие будет обработано максимум раз в секунду

Версия с сохранением последнего значения

function throttle<T extends (...args: any[]) => any>(
  fn: T,
  limit: number
): (...args: Parameters<T>) => void {
  let inThrottle: boolean = false;
  let lastArgs: Parameters<T> | null = null;

  return function (...args: Parameters<T>) {
    lastArgs = args;

    if (!inThrottle) {
      fn(...args);
      inThrottle = true;

      setTimeout(() => {
        inThrottle = false;
        // Если были вызовы во время блокировки, выполнить с последними аргументами
        if (lastArgs) {
          fn(...lastArgs);
          lastArgs = null;
        }
      }, limit);
    }
  };
}

Эта версия гарантирует, что функция выполнится с ПОСЛЕДНИМИ аргументами, даже если было много вызовов.

Версия с ведущим и хвостовым вызовом

interface ThrottleOptions {
  leading?: boolean;  // Выполнить в начале периода (default: true)
  trailing?: boolean; // Выполнить в конце периода (default: false)
}

function throttle<T extends (...args: any[]) => any>(
  fn: T,
  limit: number,
  options: ThrottleOptions = {}
): (...args: Parameters<T>) => void {
  const { leading = true, trailing = false } = options;
  let inThrottle: boolean = false;
  let lastArgs: Parameters<T> | null = null;

  return function (...args: Parameters<T>) {
    lastArgs = args;

    if (!inThrottle) {
      if (leading) {
        fn(...args);
      }
      inThrottle = true;

      setTimeout(() => {
        inThrottle = false;
        if (trailing && lastArgs) {
          fn(...lastArgs);
          lastArgs = null;
        }
      }, limit);
    }
  };
}

// Использование:
const scrollHandler = throttle(
  () => console.log('Scroll'),
  1000,
  { leading: true, trailing: true } // Вызов в начале И в конце
);

Тестирование throttle

import { describe, it, expect, vi } from 'vitest';

describe('throttle', () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });

  it('должен выполнить функцию сразу', () => {
    const fn = vi.fn();
    const throttled = throttle(fn, 1000);

    throttled(1);
    expect(fn).toHaveBeenCalledWith(1);
    expect(fn).toHaveBeenCalledTimes(1);
  });

  it('должен игнорировать вызовы в периоде throttle', () => {
    const fn = vi.fn();
    const throttled = throttle(fn, 1000);

    throttled(1);
    vi.advanceTimersByTime(100);
    throttled(2);
    vi.advanceTimersByTime(100);
    throttled(3);

    expect(fn).toHaveBeenCalledTimes(1);
    expect(fn).toHaveBeenCalledWith(1);
  });

  it('должен выполнить следующий вызов после истечения limit', () => {
    const fn = vi.fn();
    const throttled = throttle(fn, 1000);

    throttled(1);
    vi.advanceTimersByTime(1000);
    throttled(2);

    expect(fn).toHaveBeenCalledTimes(2);
    expect(fn).toHaveBeenNthCalledWith(1, 1);
    expect(fn).toHaveBeenNthCalledWith(2, 2);
  });
});

Throttle vs Debounce

АспектThrottleDebounce
ИдеяОграничивает частоту вызововОткладывает вызов до затишья
ВыполнениеМаксимум N раз в секундуОдин раз после паузы
ИспользованиеScroll, resize, mousemoveSearch input, validation
ПримерНа каждый 10-й символПосле паузы в 500мс

Throttle: первый вызов выполняется сразу, остальные блокируются на время limit.

Debounce: все вызовы откладываются, и выполняется только последний через delay после паузы.

Практические применения throttle

  • Обработка scroll события — реагировать максимум раз в 100мс
  • Обработка resize окна — пересчитывать layout максимум раз в 250мс
  • Mouse/Touch события — не обрабатывать более чем раз в 16мс для 60fps
  • API запросы — защита от flood, максимум раз в 5 сек
  • Автосохранение — сохранять максимум раз в 10 сек

Реальный пример: обработка scroll

const handleScroll = () => {
  const scrollPos = window.scrollY;
  const maxScroll = document.documentElement.scrollHeight - window.innerHeight;
  const scrollPercent = (scrollPos / maxScroll) * 100;

  console.log(`Прокручено: ${scrollPercent.toFixed(1)}%`);
};

const throttledScroll = throttle(handleScroll, 250);
window.addEventListener('scroll', throttledScroll);
// handleScroll будет вызван максимум раз в 250мс