Почему при инициации главного компонента Vue.js не устанавливается слушатель события?

Я разрабатываю приложение для Telegram с использованием Vue.js и сталкиваюсь с проблемой при работе с событием beforeunload. 

Когда я добавляю обработчик события beforeunload в метод onMounted, он не срабатывает при первом запуске приложения. Однако после перезагрузки страницы событие начинает функционировать корректно.

<pre><code class="typescript">const sendLogoutRequest = () =&gt; {
  fetch("http://localhost:3000/lastauth", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      userId: userStore.user.userId,
      authToken: userStore.authToken,
    }),
  }).catch((err) =&gt; {
    console.error("Ошибка при отправке запроса:", err);
  });
};

onMounted(async () =&gt; {
...
window.addEventListener("beforeunload",  sendLogoutRequest);
})

onBeforeUnmount(() =&gt; {
window.removeEventListener("beforeunload", sendLogoutRequest);
})</code></pre>

При этом API Telegram не предоставляет аналогичных методов и событий. В чем может заключаться причина данной проблемы? Может быть, это связано с асинхронностью процесса монтирования главного компонента?
  • 29 сентября 2024 г. 14:23
Ответы на вопрос 2
Проблема, с которой вы столкнулись, связана с тем, как браузеры обрабатывают события `beforeunload` и асинхронность JavaScript. Давайте рассмотрим несколько возможных причин и решений:

### 1. Асинхронный код в `beforeunload`

Когда вы добавляете обработчик события `beforeunload`, он должен быть синхронным. Однако ваш обработчик `sendLogoutRequest` использует `fetch`, который является асинхронной операцией. Браузеры требуют, чтобы обработчик события `beforeunload` выполнял свои действия сразу и не ожидал завершения асинхронных операций.

Несмотря на то, что вы можете инициировать `fetch`, он не должен использоваться в контексте `beforeunload`. Вместо этого вы можете использовать обработчик, который просто выполняет синхронные действия или устанавливает состояние, а затем завершает асинхронную операцию независимо от события `beforeunload`.

### 2. Пример правильно реализованного кода

Вы можете использовать `navigator.sendBeacon()` для отправки данных перед закрытием вкладки или перезагрузкой страницы. `sendBeacon` предназначен для выполнения асинхронных операций, даже когда пользователь покидает страницу. Например:

```javascript
const sendLogoutRequest = () => {
  navigator.sendBeacon("http://localhost:3000/lastauth", JSON.stringify({
    userId: userStore.user.userId,
    authToken: userStore.authToken,
  }));
};

onMounted(() => {
  window.addEventListener("beforeunload", sendLogoutRequest);
})

onBeforeUnmount(() => {
  window.removeEventListener("beforeunload", sendLogoutRequest);
});
```

### 3. Проверка работы события

Можно также проверить, работает ли событие `beforeunload` корректно. Например, добавьте простой `console.log`, чтобы увидеть, вызывается ли обработчик непосредственно перед закрытием:

```javascript
const sendLogoutRequest = (event) => {
  console.log("beforeunload triggered");
  event.preventDefault();  // Стандартный подход для `beforeunload`
  event.returnValue = '';  // Поддерживает некоторые браузеры, чтобы показать стандартный диалог
  navigator.sendBeacon("http://localhost:3000/lastauth", JSON.stringify({
    userId: userStore.user.userId,
    authToken: userStore.authToken,
  }));
};
```

### 4. Предотвращение утечек памяти

Не забудьте очищать слушатели событий с использованием `onBeforeUnmount`, что вы уже делаете. Это поможет предотвратить утечки памяти и гарантирует, что ваш код работает должным образом при переходах компонентов.

### Итог

`beforeunload` не подходит для асинхронного кода и требует синхронного выполнения. Использование `navigator.sendBeacon` позволяет избежать этой проблемы. Убедитесь, что ваши обработчики корректно настроены, и приложение реагирует на события, как ожидается.
Значит ответ до боли прост. Если запускаете приложение в дев моде, то в телеграмовском браузере надо 10 раз дождаться, пока оно отлагает. То ли дело в ngrok, то ли в самом телеграмовском браузере(скорее второе, потому что в хроме все работает великолепно). 

В итоге обработчик добавил в onMounted замыкающим звеном, после всех других функций. ВАЖНО!!! обязательно ставить после метода webapp.ready(), который просят вызывать как можно раньше (хотя в документации к сдк говорят вызывать его как можно раньше с поправкой на полную инициализацию приложения).
Похожие вопросы