Вы действительно умеете пользоваться Fetch? Отмена запросов с AbortController

2 мин

1. Fetch API — основа современных Web-запросов

Fetch API пришёл на смену устаревшему XMLHttpRequest и стал стандартным способом выполнения HTTP-запросов в браузере. Он основан на Promise и предоставляет более простой и мощный механизм запросов.

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

  • Оптимизация производительности: Когда пользователь быстро переключает страницы или вводит поисковый запрос, отмена старых, уже ненужных запросов экономит пропускную способность и ресурсы сервера.
  • Предотвращение состояний гонки: При поиске по мере ввода старый запрос может вернуться позже нового, из-за чего интерфейс покажет устаревшие данные.
  • Предотвращение утечек памяти: Если компонент размонтирован до завершения запроса, запущенного в течение его жизненного цикла, попытка обновить уже не существующий компонент может вызвать ошибку или утечку памяти.

2. AbortController — универсальный сигнал отмены

AbortController — это универсальный Web API, предоставляющий механизм для прерывания одного или нескольких Web-запросов. Он не зависит от Fetch API, но прекрасно работает вместе с Fetch.

Основная идея AbortController такова:

  1. Создать экземпляр AbortController.
  2. Получить из него объект AbortSignal.
  3. Передать AbortSignal Web API, поддерживающему прерывание, например fetch.
  4. Когда потребуется отмена, вызвать у экземпляра AbortController метод abort().

3. Совместное использование AbortController и Fetch

Рассмотрим на примере, как с помощью AbortController отменить запрос Fetch.

const controller = new AbortController();
const signal = controller.signal; // 获取信号对象

async function fetchDataWithCancellation(url) {
  try {
    console.log('Fetching data...');
    const response = await fetch(url, { signal }); // 将信号传递给 fetch
    const data = await response.json();
    console.log('Data received:', data);
    return data;
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('Fetch request was aborted.');
    } else {
      console.error('Fetch error:', error);
    }
  }
}

// 示例用法
const promise = fetchDataWithCancellation('https://jsonplaceholder.typicode.com/posts/1');

// 假设在 500 毫秒后,我们决定取消这个请求
setTimeout(() => {
  controller.abort(); // 调用 abort() 方法取消请求
  console.log('Request aborted by timeout.');
}, 500);

При вызове controller.abort() запрос fetch немедленно прерывается и выбрасывает AbortError. Эту ошибку нужно перехватить в блоке catch и проверить error.name === 'AbortError', чтобы отличить отмену от других сетевых ошибок.

4. Практические сценарии

a. Поиск по мере ввода (Search-as-you-type)

Когда пользователь быстро печатает в строке поиска, каждый ввод может запускать новый запрос. Можно отменить предыдущий незавершённый запрос и оставить только самый новый.

let currentController = null;

document.getElementById('searchInput').addEventListener('input', (event) => {
  if (currentController) {
    currentController.abort(); // 取消上一个请求
  }
  currentController = new AbortController();
  const signal = currentController.signal;

  const query = event.target.value;
  if (query.length > 2) {
    fetchDataWithCancellation(`/api/search?q=${query}`, signal);
  }
});

b. Отмена запроса при размонтировании компонента

Во фреймворках вроде React или Vue отмена незавершённых запросов, запущенных внутри компонента, при его размонтировании помогает эффективно избежать утечек памяти и ненужных обновлений интерфейса.

// React 示例
useEffect(() => {
  const controller = new AbortController();
  fetchDataWithCancellation('/api/data', controller.signal);

  return () => {
    controller.abort(); // 组件卸载时取消请求
  };
}, []);

Заключение

AbortController — незаменимый инструмент современной Web-разработки. Он предоставляет нативный механизм отмены для Fetch API и других асинхронных операций, помогая создавать более надёжные и эффективные приложения с лучшим пользовательским опытом. Умение пользоваться AbortController — один из необходимых навыков хорошего фронтенд-разработчика.