Как реализовать декоратор функции в JavaScript, ограничивающий число вызовов и вызывающий колбэк при последнем вызове
Что проверяют
Замыкания, работу с this и аккуратность в граничных случаях: что считать последним вызовом и что возвращать после лимита. Часто следом просят сделать то же для асинхронной функции или объяснить, чем это отличается от once.
Как отвечать
Сразу уточните термин: здесь «декоратор» означает функцию высшего порядка, которая оборачивает другую. Синтаксис @decorator из TC39 относится к классам и их методам, к обычным функциям он не применяется.
function limitCalls(fn, limit, onLast) {
let count = 0;
let lastResult;
return function (...args) {
if (count >= limit) return lastResult;
count += 1;
lastResult = fn.apply(this, args);
if (count === limit) onLast?.(lastResult);
return lastResult;
};
}Что стоит проговорить:
- замыкание: счётчик живёт внутри обёртки, у каждой обёрнутой функции он свой;
- контекст: обёртка объявлена через
function, а не стрелкой, и вызываетfn.apply(this, args). Иначе метод объекта потеряетthis; - после лимита: вернуть последний результат, как делает
beforeиз lodash, вернутьundefinedили бросить ошибку. Это решение, его надо озвучить, а не принять молча; - колбэк: вызывается ровно один раз, после выполнения
fn, и получает результат; - валидация:
limitдолжен быть положительным целым, иначе функция не вызовется ни разу; - асинхронность: для
asyncфункции решите, считать вызов в момент старта или после успешного завершения.
Тесты стоит перечислить сами: лимит 1, вызов после лимита, сохранение this, исключение внутри fn.
Где спотыкаются
Вызывают fn(...args) или пишут обёртку стрелкой, и метод, переданный в декоратор, падает на обращении к this.
Выносят счётчик за пределы limitCalls. Тогда все обёрнутые функции делят один лимит, и баг проявляется только при второй обёртке.
Путают границы: сравнивают count > limit до инкремента или вызывают колбэк на каждом вызове после лимита. Проговорите на примере с лимитом 3, какие вызовы проходят.