> Как реализовать функцию-обертку runOnce в JavaScript, вызывающую функцию только один раз? (JavaScript)
Уровень: senior · Роль: frontend · Категория: Технические вопросы
Компании: Яндекс
Стек: JavaScript
> Пример ответа
Короткий ответ
Функция-обертка runOnce в JavaScript вызывается только один раз, после чего последующие вызовы игнорируются или возвращают кешированный результат. Реализуется через замыкание с флагом called и сохранением результата первого вызова. Для сложных сценариев используется WeakMap для привязки к объектам. Типичная реализация - once из lodash.
Подробное объяснение
runOnce - это higher-order function, которая принимает функцию fn и возвращает новую функцию с модифицированным поведением: при первом вызове выполняется fn и запоминается её результат, при последующих вызовах fn не вызывается, а возвращается сохранённый результат. Это реализуется через замыкание, где хранятся флаги called и result.
Базовый механизм: при каждом вызове обёртки проверяется called. Если false, вызывается fn, результат сохраняется в result, called устанавливается в true. Если true, возвращается result. Важно обрабатывать контекст (this) и аргументы через apply или call.
Для методов объектов используется WeakMap для хранения состояния на экземпляр, чтобы избежать утечек памяти. В современных фреймворках (React, Vue) runOnce применяется для однократной инициализации, подписок или загрузки данных.
На практике
В продакшене обычно используют готовые реализации из lodash/underscore или пишут кастомную с учётом:
- корректной передачи
thisи аргументов - обработки ошибок (если
fnвыбросила исключение при первом вызове, флагcalledне должен устанавливаться) - поддержки асинхронных функций (возврат промиса)
- опционального сброса состояния для тестирования
Примеры использования:
- однократная инициализация WebSocket соединения
- загрузка конфигурации при старте приложения
- debounce с гарантией единственного вызова
- подписка на события, которая должна сработать только раз
Пример кода
JAVASCRIPT// Базовая реализацияfunction runOnce(fn) {let called = false;let result;return function(...args) {if (!called) {called = true;result = fn.apply(this, args);}return result;};}// С обработкой ошибокfunction runOnceSafe(fn) {let called = false;let result;return function(...args) {if (!called) {called = true;try {result = fn.apply(this, args);} catch (error) {called = false; // сброс флага при ошибкеthrow error;}}return result;};}// Для методов объектов с WeakMapconst onceMap = new WeakMap();function runOnceMethod(fn) {return function(...args) {if (!onceMap.has(this)) {onceMap.set(this, { called: false, result: undefined });}const state = onceMap.get(this);if (!state.called) {state.called = true;state.result = fn.apply(this, args);}return state.result;};}
Как отвечать на собеседовании
Начни с базового определения и простой реализации через замыкание. Покажи понимание передачи this и аргументов. Затем обсуди edge cases: ошибки, асинхронность, утечки памяти. Упомяни WeakMap для методов объектов. Сравни с готовыми решениями из lodash. Если спросят про тестирование - скажи про сброс состояния через reset или создание новой обёртки.
Что проверяет интервьюер
- понимание замыканий и области видимости
- умение работать с
thisи контекстом вызова - знание паттернов higher-order functions
- внимание к edge cases (ошибки, асинхронность, мультиинстансность)
- способность проектировать API (опции, reset, совместимость)
- понимание memory management (WeakMap, утечки)
Типичные ошибки
- потеря контекста
thisпри вызовеfn(использование стрелочной функции в обёртке) - отсутствие обработки ошибок - флаг
calledустанавливается даже при исключении - игнорирование аргументов - передача только первого аргумента вместо всех через rest/spread
- мутация внешнего состояния без необходимости
- использование глобальных переменных вместо замыкания
- неправильная работа с асинхронными функциями (не возвращается промис)
> Похожие задачи по frontend
Почему garbage collector не очищает переменную в случае замыкания в JavaScript
Какой числовой формат даты подходит для сравнения объектов Date в JavaScript
Какие подводные камни есть при использовании ссылочных типов в JavaScript кроме сравнения
Что происходит с функцией в Map, пока на Map есть ссылка в JavaScript
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью