В контексте разработки веб-приложений часто возникает необходимость управлять асинхронными операциями так, чтобы они могли быть безопасно остановлены при изменении состояния компонента. В библиотеке Haunted, построенной на основе Web Components и Hooks, управление асинхронными запросами приобретает особую значимость, поскольку компоненты могут быстро монтироваться и размонтироваться.
AbortController для отмены запросовОсновной инструмент для отмены асинхронных операций в браузере —
AbortController. Он позволяет создать
сигнал, который можно передать в функцию fetch или любой
другой API, поддерживающий отмену:
import { html, component, useEffect } from 'haunted';
function FetchData({ url }) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
fetch(url, { signal })
.then(response => response.json())
.then(setData)
.catch(err => {
if (err.name !== 'AbortError') {
setError(err);
}
});
return () => {
controller.abort();
};
}, [url]);
if (error) return html`<p>Ошибка: ${error.message}</p>`;
if (!data) return html`<p>Загрузка...</p>`;
return html`<pre>${JSON.stringify(data, null, 2)}</pre>`;
}
customElements.define('fetch-data', component(FetchData));
В этом примере:
AbortController создаётся внутри useEffect
для каждой загрузки данных.fetch.useEffect гарантирует отмену запроса
при размонтировании компонента или при изменении url.AbortError игнорируется, так как оно
является нормальным результатом отмены.Не только fetch поддерживает AbortSignal.
Можно создавать собственные асинхронные функции, которые реагируют на
сигнал:
function delayedTask(ms, signal) {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => resolve('Готово'), ms);
signal.addEventListener('abort', () => {
clearTimeout(timeout);
reject(new DOMException('Операция отменена', 'AbortError'));
});
});
}
function TimerComponent({ delay }) {
const [message, setMessage] = useState('Ожидание...');
useEffect(() => {
const controller = new AbortController();
delayedTask(delay, controller.signal)
.then(setMessage)
.catch(err => {
if (err.name !== 'AbortError') setMessage(`Ошибка: ${err.message}`);
});
return () => controller.abort();
}, [delay]);
return html`<p>${message}</p>`;
}
customElements.define('timer-component', component(TimerComponent));
Ключевые моменты:
AbortSignal.controller.abort() при
размонтировании компонента.Если компонент инициирует несколько асинхронных операций
одновременно, можно объединять их с помощью одного
AbortController:
useEffect(() => {
const controller = new AbortController();
const { signal } = controller;
const urls = ['/api/users', '/api/posts'];
Promise.all(urls.map(url =>
fetch(url, { signal }).then(res => res.json())
))
.then(results => setData({ users: results[0], posts: results[1] }))
.catch(err => {
if (err.name !== 'AbortError') setError(err);
});
return () => controller.abort();
}, []);
Это гарантирует, что при размонтировании все запросы будут остановлены одновременно.
AbortController внутри
useEffect. Никогда не использовать один и тот же
контроллер для разных эффектов, чтобы избежать конфликтов.AbortError отдельно. Он
не является ошибкой приложения и должен игнорироваться при отображении
сообщений об ошибках.signal в пользовательские
асинхронные функции, если планируется их отмена при
размонтировании компонента.Haunted не предоставляет встроенного механизма отмены асинхронных
операций, но сочетание useEffect и
AbortController позволяет полностью контролировать
жизненный цикл запросов. Это критично для динамических приложений, где
компоненты часто монтируются и размонтируются, а асинхронные операции
могут продолжаться после удаления DOM-узлов. Такой подход повышает
производительность, надёжность и предотвращает типичные ошибки
обновления состояния после размонтирования.