Отмена запросов

В современных веб-приложениях часто возникает необходимость прерывать асинхронные запросы, особенно при работе с динамическими компонентами, автозаполнением или поиском. Vue.js предоставляет удобный инструментарий для интеграции таких механизмов, используя возможности JavaScript и сторонние библиотеки, такие как Axios или Fetch API.

Проблема асинхронных запросов

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

export default {
  data() {
    return {
      results: [],
      query: '',
    };
  },
  watch: {
    query(newQuery) {
      this.fetchData(newQuery);
    }
  },
  methods: {
    async fetchData(query) {
      const response = await fetch(`https://api.example.com/search?q=${query}`);
      const data = await response.json();
      this.results = data.items;
    }
  }
};

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

Использование AbortController с Fetch API

JavaScript предоставляет встроенный объект AbortController для отмены запросов:

export default {
  data() {
    return {
      results: [],
      query: '',
      controller: null,
    };
  },
  watch: {
    query(newQuery) {
      this.fetchData(newQuery);
    }
  },
  methods: {
    async fetchData(query) {
      if (this.controller) {
        this.controller.abort(); // Отменяем предыдущий запрос
      }
      this.controller = new AbortController();
      try {
        const response = await fetch(`https://api.example.com/search?q=${query}`, {
          signal: this.controller.signal
        });
        const data = await response.json();
        this.results = data.items;
      } catch (error) {
        if (error.name === 'AbortError') {
          console.log('Запрос отменен');
        } else {
          console.error(error);
        }
      }
    }
  }
};

Ключевые моменты:

  • Каждый новый запрос создает новый AbortController.
  • Сигнал контроллера передается в fetch.
  • Обработка исключения AbortError предотвращает появление ошибок в консоли.

Отмена запросов в Axios

Axios также поддерживает отмену запросов через CancelToken или современный AbortController (начиная с версии 1.2):

import axios from 'axios';

export default {
  data() {
    return {
      results: [],
      query: '',
      cancelTokenSource: null,
    };
  },
  watch: {
    query(newQuery) {
      this.fetchData(newQuery);
    }
  },
  methods: {
    async fetchData(query) {
      if (this.cancelTokenSource) {
        this.cancelTokenSource.cancel('Предыдущий запрос отменен');
      }
      this.cancelTokenSource = axios.CancelToken.source();
      try {
        const response = await axios.get(`https://api.example.com/search?q=${query}`, {
          cancelToken: this.cancelTokenSource.token
        });
        this.results = response.data.items;
      } catch (error) {
        if (axios.isCancel(error)) {
          console.log(error.message);
        } else {
          console.error(error);
        }
      }
    }
  }
};

Интеграция с жизненным циклом компонентов

Важно отменять запросы при уничтожении компонента, чтобы избежать ошибок:

export default {
  data() {
    return {
      controller: null,
    };
  },
  methods: {
    async fetchData() {
      this.controller = new AbortController();
      await fetch('https://api.example.com/data', {
        signal: this.controller.signal
      });
    }
  },
  beforeUnmount() {
    if (this.controller) {
      this.controller.abort();
    }
  }
};

Использование beforeUnmount гарантирует, что запрос не продолжит выполняться после удаления компонента из DOM, предотвращая утечки памяти и неожиданные изменения состояния.

Комбинация с watch и дебаунсом

Для улучшения производительности и снижения количества запросов часто применяют дебаунс (задержку перед вызовом метода):

import { ref, watch } from 'vue';
import debounce from 'lodash/debounce';

export default {
  setup() {
    const query = ref('');
    const results = ref([]);
    let controller = null;

    const fetchData = debounce(async (value) => {
      if (controller) controller.abort();
      controller = new AbortController();
      try {
        const response = await fetch(`https://api.example.com/search?q=${value}`, {
          signal: controller.signal
        });
        const data = await response.json();
        results.value = data.items;
      } catch (error) {
        if (error.name !== 'AbortError') console.error(error);
      }
    }, 300);

    watch(query, (newQuery) => {
      fetchData(newQuery);
    });

    return { query, results };
  }
};

Выводы по практике:

  • Использование AbortController или CancelToken предотвращает гонки асинхронных запросов.
  • Отмена запросов снижает нагрузку на сервер и повышает отзывчивость интерфейса.
  • Комбинация с жизненным циклом компонентов и дебаунсом обеспечивает корректное поведение в динамичных приложениях.