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 не будет опубликован. Обязательные поля помечены *