Как реализовать debounce?

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

let counter = 0;
const fn = () => {
  counter++;
};
 
const debouncedFn = debounce(fn, 200);
debouncedFn(); // первый вызов
setTimeout(debouncedFn, 100); // вызов через 100 мс после последнего вызова
// первый вызов был заблокирован, второй ожидает окончания таймера
setTimeout(debouncedFn, 200); // вызов через 100 мс после последнего вызова
// второй вызов был заблокирован, третий ожидает окончания таймера
setTimeout(debouncedFn, 300); // ...
setTimeout(debouncedFn, 400); // после этого вызова не следует других вызовов
// только этот вызов сработает, т.к. после него прошло 200 мс и других вызовов не было
console.log(counter); // 1

Мой код:

const debounce = (fn, debounceTime) => {
    let count = -Infinity;
    let res;
    return function() {
        const end = Date.now();
        if (end - count >= debounceTime) {
            count = end;
            res = fn.apply(this, arguments);
        }
        return res;
    };
};

Не проходит тест "должна блокировать вызовы функции в течение времени задержки, пока функция вызывается снова ранее, чем прошло время задержки", что не так?


Ответы (2 шт):

Автор решения: SwaD

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

let counter = 0;
const fnс = () => {
  counter++;
};

// Каждый следующий вызов отменяет предыдущий, если он ожидает выполнения
const debounce = (fn, debounceTime) => {
  let timer;
  return function() {
    clearInterval(timer);
    timer = setTimeout(fn, debounceTime);
  };
};

const debouncedFn = debounce(fnс, 200);
debouncedFn(); // первый вызов
setTimeout(debouncedFn, 100); // вызов через 100 мс после последнего вызова
// первый вызов был заблокирован, второй ожидает окончания таймера
setTimeout(debouncedFn, 200); // вызов через 100 мс после последнего вызова
// второй вызов был заблокирован, третий ожидает окончания таймера
setTimeout(debouncedFn, 300); // ...
setTimeout(debouncedFn, 400); // после этого вызова не следует других вызовов
// только этот вызов сработает, т.к. после него прошло 200 мс и других вызовов не было

console.log('Ждем окончания');

setTimeout(() => {
  console.log('CNT', counter); // 1
}, 1000);

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

const debounce = (fn, debounceTime) => {
  let isWait = false;

  return function() {
    if (!isWait) {
      isWait = true;
      fn();
      setTimeout(() => isWait = false, debounceTime);
    }
  };
};
→ Ссылка
Автор решения: Stanislav Volodarskiy

Всё у вас правильно, кроме следующего момента: если вы решаете не вызывать функцию, вы возвращаете результат её последнего вызова. Тестирующая система такое решение не примет. Правильно возвращать undefined если функцию вызывать рано:

const debounce = (fn, debounceTime) => {
    let count = -Infinity;
    return function() {
        const end = Date.now();
        let res = undefined;
        if (end - count >= debounceTime) {
            count = end;
            res = fn.apply(this, arguments);
        }
        return res;
    };
};

Ещё мелочь: если запоминать не время вызова а время когда вызывать fn будет можно, то можно немного упростить код:

const debounce = (fn, delay) => {
    let deadline = 0;
    return () => {
        const now = Date.now();
        if (now < deadline) {
            return undefined;
        }
        deadline = now + delay;
        return fn.apply(this, arguments);
    };
};
→ Ссылка