Типова задача: при зміні id завантажити дані. Якщо id змінюється швидко (користувач клацає по списку), запити летять паралельно, і відповідь на старий запит може прийти пізніше за новий - на екрані опиняться дані не того запису.
watch(id, async (newId) => {
const response = await fetch(`/api/users/${newId}`);
user.value = await response.json(); // може перезаписати свіжі дані старими
});
Очищення побічного ефекту. Перед наступним запуском спостерігача (і при його зупинці) Vue викликає функцію очищення - туди кладуть скасування попереднього запиту:
import { watch, onWatcherCleanup } from 'vue';
watch(id, async (newId) => {
const controller = new AbortController();
onWatcherCleanup(() => controller.abort()); // Vue 3.5+
try {
const response = await fetch(`/api/users/${newId}`, { signal: controller.signal });
user.value = await response.json();
} catch (error) {
if (error.name !== 'AbortError') throw error;
}
});
Важливо: onWatcherCleanup треба викликати синхронно, до першого await - після нього Vue вже не знає, до якого спостерігача він належить.
До Vue 3.5 - третій аргумент колбеку:
watch(id, async (newId, oldId, onCleanup) => {
const controller = new AbortController();
onCleanup(() => controller.abort());
// ...
});
Цей варіант працює й зараз і не має обмеження щодо await.
Інші застосування очищення: зняти таймер (clearInterval), відписатися від WebSocket-каналу, знищити екземпляр сторонньої бібліотеки, створений для попереднього значення.
Зупинка спостерігача:
- спостерігачі, створені синхронно в
setup, зупиняються автоматично разом з компонентом; - створені асинхронно (після
awaitчи вsetTimeout) - ні: їх треба зупинити вручну, інакше витік пам'яті:
const stop = watch(source, callback);
stop();
У Vue 3.5 повернений об'єкт має ще й pause() і resume().
Альтернатива для даних, що залежать від параметрів, - бібліотеки на кшталт TanStack Query, що вже розв'язують гонитву, кешування й повтори.
Докладніше в документації: Спостерігачі: очищення побічних ефектів