Одноразовые события

В архитектуре событийной модели Google Maps JavaScript API ключевую роль играет возможность регистрации обработчиков, которые автоматически удаляются после первого срабатывания. Такой подход используется для сценариев, где повторная реакция на событие не требуется: инициализация карты, первичная загрузка тайлов, получение геолокации, однократная подстройка интерфейса.

Событийная система построена поверх пространства google.maps.event, которое предоставляет методы подписки и управления жизненным циклом обработчиков. В классической модели используются addListener и removeListener, однако для одноразовых сценариев предусмотрен специализированный механизм — addListenerOnce.


Отличие одноразовых событий от постоянных подписок

Постоянные обработчики остаются активными до явного удаления:

const listener = map.addListener("click", (e) => {
  console.log("Клик по карте", e.latLng);
});

// удаление при необходимости
google.maps.event.removeListener(listener);

Одноразовый обработчик автоматически удаляется после первого вызова:

map.addListenerOnce("click", (e) => {
  console.log("Сработает только один раз", e.latLng);
});

Ключевое отличие заключается не только в удобстве синтаксиса, но и в управлении памятью: исключается необходимость ручного вызова удаления.


Внутренний смысл addListenerOnce

Метод addListenerOnce фактически оборачивает стандартный обработчик, добавляя промежуточную функцию-декоратор. После первого вызова происходит:

  1. выполнение пользовательского callback;
  2. немедленное удаление слушателя;
  3. освобождение ссылки на обработчик.

Такой механизм особенно важен в долгоживущих SPA-приложениях, где утечки памяти приводят к накоплению неиспользуемых замыканий.


Типовые сценарии применения

Первичная инициализация карты

Однократное ожидание полной загрузки состояния карты:

map.addListenerOnce("idle", () => {
  console.log("Карта полностью загружена и стабилизирована");
});

Событие idle срабатывает после завершения всех трансформаций viewport, включая загрузку тайлов и завершение анимаций.


Однократное получение границ карты

map.addListenerOnce("bounds_changed", () => {
  const bounds = map.getBounds();
  console.log("Границы получены", bounds);
});

Использование одноразового обработчика предотвращает многократные вызовы при серии изменений масштаба.


Однократная реакция на геометрическое событие

marker.addListenerOnce("dragend", (e) => {
  console.log("Маркер перемещён один раз", e.latLng);
});

Управление жизненным циклом обработчиков

Несмотря на автоматическое удаление, одноразовые события участвуют в общей системе управления слушателями. Объект, возвращаемый при подписке, остаётся идентичным стандартному MVCEventListener, что позволяет унифицировать подход к логированию и отладке.


Сравнение addListener и addListenerOnce

Характеристика addListener addListenerOnce
Количество вызовов неограниченное один
Требуется удаление да нет
Риск утечек выше минимальный
Использование памяти стабильное при удалении автоматически очищается

Особенности поведения в асинхронных сценариях

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

function waitForMapIdle(map) {
  return new Promise((resolve) => {
    map.addListenerOnce("idle", () => {
      resolve("ready");
    });
  });
}

Такой паттерн превращает событийную модель в управляемый поток выполнения.


Сочетание с динамическими изменениями состояния карты

При работе с динамическими изменениями (zoom, center, bounds) одноразовые обработчики позволяют фиксировать конкретный этап взаимодействия:

map.setZoom(10);

map.addListenerOnce("zoom_changed", () => {
  console.log("Zoom установлен и применён");
});

Это исключает повторные вызовы при инерционных изменениях масштаба.


Управление ссылками и утечками памяти

В долгоживущих приложениях ключевой проблемой становится накопление подписок. Даже при использовании removeListener возможны ситуации, когда ссылки на callback сохраняются в замыканиях.

Одноразовые события решают проблему на уровне API: после вызова происходит разрыв цепочки ссылок внутри диспетчера событий. Это снижает вероятность:

  • удержания DOM-узлов через замыкания;
  • накопления неиспользуемых слушателей;
  • повторного срабатывания логики инициализации.

Реализация через стандартный event namespace

Внутри google.maps.event одноразовые обработчики реализуются через обёртку:

function once(instance, eventName, handler) {
  const wrapper = (event) => {
    google.maps.event.removeListener(listener);
    handler(event);
  };

  const listener = instance.addListener(eventName, wrapper);
  return listener;
}

Подобная схема демонстрирует принцип: удаление происходит до или сразу после выполнения callback.


Поведение при ошибках внутри обработчика

Если внутри одноразового callback возникает исключение, удаление слушателя всё равно выполняется, поскольку отписка происходит на уровне wrapper-функции. Это предотвращает повторное срабатывание даже при аварийном завершении логики.


Взаимодействие с несколькими событиями

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

map.addListenerOnce("tilesloaded", () => {
  map.addListenerOnce("idle", () => {
    console.log("Сначала тайлы, затем стабилизация");
  });
});

Такая композиция позволяет строить последовательные цепочки состояний карты без внешнего менеджера событий.


Интеграция с внешними потоками данных

Одноразовые события часто используются при синхронизации с внешними API:

fetch("/geo.json")
  .then(res => res.json())
  .then(() => {
    map.addListenerOnce("idle", () => {
      console.log("Данные загружены и карта стабилизирована");
    });
  });

Здесь событие выступает как точка фиксации состояния после завершения загрузки данных и рендеринга.


Поведение при повторной регистрации

Повторный вызов addListenerOnce создаёт независимый обработчик. Каждый из них сработает один раз, независимо от других подписок:

map.addListenerOnce("click", () => console.log("A"));
map.addListenerOnce("click", () => console.log("B"));

При первом клике оба обработчика будут активированы и затем удалены.


Документация и спецификация поведения событий

Официальная модель событий описана в документации платформы Google Maps:

Google Maps JavaScript API Documentation