Обработка асинхронных запросов

Асинхронные операции в Vue.js лежат в основе работы с сервером, пользовательскими событиями и реактивным обновлением интерфейса. Любое взаимодействие с API, таймерами, WebSocket или ленивой загрузкой компонентов строится на неблокирующей модели выполнения JavaScript. Vue не управляет асинхронностью напрямую, но предоставляет удобные механизмы для корректной интеграции асинхронного кода в реактивную систему.


Использование Promise и async/await в компонентах

На практике асинхронные запросы чаще всего реализуются через fetch, axios или аналогичные библиотеки, возвращающие Promise.

export default {
  data() {
    return {
      users: [],
      loading: false,
      error: null
    }
  },
  async mounted() {
    this.loading = true
    try {
      const response = await fetch('/api/users')
      this.users = await response.json()
    } catch (e) {
      this.error = e
    } finally {
      this.loading = false
    }
  }
}

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

  • асинхронная функция может использоваться в хуках жизненного цикла;
  • изменение реактивных данных внутри await-блоков корректно отражается в DOM;
  • обработка ошибок должна быть явной, так как необработанный Promise rejection не перехватывается Vue.

Асинхронные методы компонента

Методы компонента поддерживают асинхронность без ограничений. Это удобно при обработке пользовательских действий.

methods: {
  async saveForm() {
    this.saving = true
    try {
      await api.save(this.form)
      this.success = true
    } catch (e) {
      this.errorMessage = 'Ошибка сохранения'
    } finally {
      this.saving = false
    }
  }
}

Асинхронные методы:

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

Управление состоянием загрузки

Асинхронные запросы почти всегда сопровождаются состояниями:

  • загрузка данных;
  • успешное завершение;
  • ошибка.

На уровне компонента это обычно реализуется через дополнительные реактивные свойства.

<div v-if="loading">Загрузка...</div>
<div v-else-if="error">Ошибка</div>
<ul v-else>
  <li v-for="item in items" :key="item.id">
    {{ item.name }}
  </li>
</ul>

Такой подход позволяет декларативно связать состояние асинхронной операции с отображением.


Асинхронные вычисляемые данные

Классические computed свойства не предназначены для асинхронного кода. Возврат Promise нарушает ожидаемую синхронную модель.

Неправильный пример:

computed: {
  async user() {
    const res = await fetch('/api/user')
    return res.json()
  }
}

Корректный подход:

  • асинхронный запрос выполняется в методе или хуке;
  • результат сохраняется в data;
  • computed используется для синхронных производных значений.

watch и асинхронные побочные эффекты

Наблюдатели (watch) часто применяются для запуска асинхронных операций при изменении состояния.

watch: {
  query: {
    immediate: true,
    async handler(newValue) {
      this.loading = true
      this.results = await searchApi(newValue)
      this.loading = false
    }
  }
}

Особенности:

  • watch подходит для реакций на конкретные изменения;
  • возможны гонки запросов при быстрых изменениях значения;
  • требуется ручная отмена или игнорирование устаревших запросов.

Предотвращение гонок запросов

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

Распространённые решения:

  • использование AbortController;
  • проверка актуальности запроса;
  • хранение идентификатора последнего запроса.
let currentRequest = 0

async function loadData(query) {
  const requestId = ++currentRequest
  const data = await api.fetch(query)
  if (requestId === currentRequest) {
    this.items = data
  }
}

Асинхронные компоненты

Vue поддерживает ленивую загрузку компонентов, что также является формой асинхронной обработки.

const AsyncComponent = () => import('./HeavyComponent.vue')

Расширенный вариант:

const AsyncComponent = defineAsyncComponent({
  loader: () => import('./HeavyComponent.vue'),
  loadingComponent: Loader,
  errorComponent: ErrorBlock,
  delay: 200,
  timeout: 3000
})

Это позволяет:

  • уменьшать начальный размер бандла;
  • управлять состояниями загрузки и ошибки на уровне компонента;
  • улучшать восприятие производительности.

Асинхронные действия во Vuex и Pinia

При использовании централизованного хранилища асинхронная логика выносится из компонентов.

Vuex

actions: {
  async fetchPosts({ commit }) {
    const posts = await api.getPosts()
    commit('setPosts', posts)
  }
}

Pinia

actions: {
  async loadUser() {
    this.user = await api.getUser()
  }
}

Преимущества:

  • единая точка управления побочными эффектами;
  • упрощённое тестирование;
  • повторное использование логики.

Асинхронность и жизненный цикл

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

Типичная проблема:

  • запрос завершается;
  • попытка изменить состояние уничтоженного компонента.

Практики защиты:

  • флаг isMounted;
  • отмена запросов в beforeUnmount;
  • проверка актуальности компонента перед обновлением данных.
let active = true

onMounted(async () => {
  const data = await api.load()
  if (active) state.data = data
})

onBeforeUnmount(() => {
  active = false
})

Асинхронные слоты и Suspense

Vue 3 предоставляет компонент Suspense для работы с асинхронными зависимостями.

<Suspense>
  <template #default>
    <AsyncComponent />
  </template>
  <template #fallback>
    <Loader />
  </template>
</Suspense>

Suspense:

  • ожидает завершения асинхронных зависимостей;
  • блокирует рендер до готовности;
  • упрощает управление сложными деревьями компонентов с загрузкой данных.

Интеграция с внешними асинхронными источниками

Работа с WebSocket, EventSource или подписками требует особого подхода:

  • инициализация при монтировании;
  • корректное закрытие соединений;
  • синхронизация входящих данных с реактивным состоянием.
onMounted(() => {
  socket = new WebSocket(url)
  socket.onmess age = e => {
    messages.push(JSON.parse(e.data))
  }
})

onBeforeUnmount(() => {
  socket.close()
})

Асинхронность в этом случае становится постоянным процессом, а не разовой операцией.


Ошибки асинхронного кода и глобальная обработка

Vue позволяет перехватывать ошибки асинхронных операций через глобальные обработчики.

app.config.errorHandler = (err, instance, info) => {
  logError(err)
}

Это полезно для:

  • логирования ошибок API;
  • отображения уведомлений;
  • централизованного контроля сбоев.

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