AbortController для отмены pending запросов при смене страницы (SPA)

от автора

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

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

А иногда это приводит и к ошибкам. Например, у нас есть две страницы — страница соревнований и страница турниров, обе страницы имеют id в URL и данные запрашиваются по этому id. Когда пользователь быстро переходит со страницы турнира на страницу соревнования, то id турнира может подмениться на id соревнования, и тогда получится, что запрос пойдет на несуществующий для турнира id и упадет ошибка.

Когда я столкнулся с этой проблемой, то первой мыслью было использовать AbortController для отмены таких “висящих” запросов, и я решил загуглить как это правильно сделать, но сразу же был крайне удивлен тем, что советы на stackoverflow или различные статьи как-то очень странно и скудно освещали эту проблему, иногда предлагая абсолютно нерабочие решения, поэтому и решено было написать эту статью, чтобы показать конкретный рабочий пример.

Для тех, кто с этим не знаком, AbortController — это, простыми словами, интерфейс, который позволяет управлять отменой http запросов со стороны фронтенда.

Итак, для начала нам нужно создать сервис, который бы создавал экземпляр нашего контроллера и экспортировал методы для управления им:

// abortController.js  let controller = new AbortController()  export const getControllerSignal = () => controller.signal  export const abortController = () => controller.abort()  export const reinitController = () => {   controller = new AbortController() }

Пройдемся по всему по порядку:

  • signal нам нужен для передачи его в запрос, чтобы в дальнейшем мы могли этим запросом управлять и отменять его

  • метод abort() нужен собственно для отмены запроса, в который мы передали signal

  • и важный момент — для того чтобы запросы прерывались не навсегда после первого раза, а только в нужный момент, после его отмены нам нужно заново проинициализировать наш контроллер, для этого и нужен метод reinitController, который просто вызывает new AbortController()

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

// apiHelper.js  import { getControllerSignal } from '@/services/abortController'  export const callPrivateApi = async(method, url, ...args) => {   try {     const { data } = await privateApi[method](url, ...[...args, { signal: getControllerSignal() }])     return { data, error: null }   } catch ({ response }) {     return {       data: null,       error: {         text: response?.data,         status: response?.status,       },     }   } }

У нас проект на Vue.js, поэтому далее будет пример кода именно для него.

В файле роутера воспользуемся хуком beforeEach, который будет срабатывать при каждой смене страницы, в нем нам нужно будет отменить запросы, а также заново проинициализировать контроллер:

// router.js  import { abortController, reinitController } from '@/services/abortController'  ...  router.beforeEach(() => {   abortController()   reinitController() })

Вот и все! Теперь все незаконченные (pending) запросы будут прерываться при смене страницы, не вызывая ненужных ошибок и не используя зря сеть пользователей вебсайта.


ссылка на оригинал статьи https://habr.com/ru/articles/746740/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *