Pull to refresh

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

Level of difficultyMedium
Reading time2 min
Views7.6K

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

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

Первой мыслью было использовать 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() }])
    ...
  } catch ({ response }) {
    ...
  }
}

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

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

// router.js

import { abortController, reinitController } from '@/services/abortController'

...

router.beforeEach(() => {
  abortController()
  reinitController()
})

Теперь все незаконченные (pending) запросы будут прерываться при смене страницы, а выглядеть эти запросы будут вот так, со статусом canceled:

Важно: на всякий случай добавлю, что нужно обращать внимание, есть ли у вас какие-то запросы, которые должны выполниться в любом случае (например, запрос для авторизации/получения данных юзера, происходящий одновременно с редиректом), чтобы не столкнуться с последствиями их отмены.

Tags:
Hubs:
Total votes 5: ↑4 and ↓1+3
Comments10

Articles