Работа с асинхронными действиями

Асинхронные действия во Vue.js возникают при работе с HTTP-запросами, таймерами, событиями браузера, динамическим импортом компонентов и асинхронной инициализацией состояния. Архитектура фреймворка позволяет встроить асинхронную логику на уровне компонентов, хранилищ состояния и маршрутизации без нарушения реактивности.


Асинхронные функции и реактивность

Vue корректно отслеживает изменения, происходящие после завершения асинхронных операций. Главное требование — изменение должно происходить через реактивные источники данных.

import { ref } from 'vue'

const data = ref(null)
const loading = ref(false)
const error = ref(null)

async function loadData() {
  loading.value = true
  error.value = null

  try {
    const response = await fetch('/api/items')
    data.value = await response.json()
  } catch (e) {
    error.value = e
  } finally {
    loading.value = false
  }
}

ref и reactive сохраняют связь между асинхронным результатом и шаблоном, даже если данные обновляются спустя значительное время.


Асинхронные действия в жизненном цикле компонента

Для загрузки данных при создании компонента используются хуки жизненного цикла.

Composition API

import { onMounted, ref } from 'vue'

const users = ref([])

onMounted(async () => {
  const res = await fetch('/api/users')
  users.value = await res.json()
})

Options API

export default {
  data() {
    return {
      users: []
    }
  },
  async mounted() {
    const res = await fetch('/api/users')
    this.users = await res.json()
  }
}

Асинхронность в хуках не блокирует отрисовку компонента. Шаблон обновляется по мере поступления данных.


Асинхронная функция setup

setup может быть объявлена как async. В этом случае компонент будет ожидать её завершения только при использовании <Suspense>.

export default {
  async setup() {
    const res = await fetch('/api/config')
    const config = await res.json()

    return { config }
  }
}

Без <Suspense> асинхронный setup допустим, но теряется возможность управления состоянием загрузки на уровне шаблона.


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

<Suspense> позволяет отложить рендер компонента до завершения асинхронных зависимостей.

<Suspense>
  <template #default>
    <AsyncComponent />
  </template>
  <template #fallback>
    <div>Загрузка…</div>
  </template>
</Suspense>

Компонент внутри default может содержать:

  • асинхронный setup
  • асинхронные компоненты (defineAsyncComponent)

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

Динамическая загрузка компонентов снижает размер начального бандла.

import { defineAsyncComponent } from 'vue'

const UserProfile = defineAsyncComponent(() =>
  import('./UserProfile.vue')
)

Поддерживаются:

  • собственный компонент загрузки
  • таймауты
  • обработка ошибок
defineAsyncComponent({
  loader: () => import('./Dashboard.vue'),
  delay: 200,
  timeout: 3000
})

Обработка ошибок в асинхронных действиях

Ошибки из async/await не перехватываются автоматически Vue. Используются стандартные конструкции JavaScript.

try {
  await fetchData()
} catch (err) {
  console.error(err)
}

Для ошибок рендера применяется errorCaptured или глобальные обработчики:

app.config.errorHandler = (err) => {
  console.error(err)
}

Асинхронность и watch

watch поддерживает асинхронные колбэки и отмену предыдущих вызовов.

watch(query, async (newValue, oldValue, onInvalidate) => {
  const controller = new AbortController()

  onInvalidate(() => controller.abort())

  const res = await fetch(`/api/search?q=${newValue}`, {
    signal: controller.signal
  })

  results.value = await res.json()
})

Такой подход предотвращает гонки запросов при быстром изменении наблюдаемого значения.


Дебаунс и троттлинг асинхронных операций

Частые асинхронные вызовы оптимизируются через debounce или throttle.

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

const term = ref('')

const search = debounce(async (value) => {
  await fetch(`/api/search?q=${value}`)
}, 300)

watch(term, (v) => search(v))

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

Pinia использует асинхронные функции как действия без дополнительных обёрток.

import { defineStore } from 'pinia'

export const useUserStore = defineStore('users', {
  state: () => ({
    list: [],
    loading: false
  }),
  actions: {
    async fetchUsers() {
      this.loading = true
      this.list = await fetch('/api/users').then(r => r.json())
      this.loading = false
    }
  }
})

Асинхронные действия легко комбинируются, возвращают Promise и могут вызываться друг из друга.


Асинхронность и маршрутизация

Vue Router поддерживает асинхронные хуки навигации.

router.beforeEach(async (to, from) => {
  const isAuth = await checkAuth()
  if (!isAuth && to.meta.requiresAuth) {
    return '/login'
  }
})

Также маршруты могут содержать асинхронные компоненты:

{
  path: '/admin',
  component: () => import('./views/Admin.vue')
}

Отмена асинхронных операций при размонтировании

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

import { onUnmounted } from 'vue'

const controller = new AbortController()

fetch('/api/data', { signal: controller.signal })

onUnmounted(() => {
  controller.abort()
})

Это особенно важно при работе с запросами, таймерами и подписками.


Асинхронность и серверный рендеринг

При SSR асинхронные операции должны завершаться до генерации HTML. Vue автоматически ожидает:

  • асинхронный setup
  • асинхронные хуки маршрутов

Рекомендуется выносить загрузку данных в функции, совместимые и с клиентом, и с сервером, избегая прямого доступа к API браузера.


Структурирование асинхронной логики

Для крупных приложений асинхронная логика выносится:

  • в composable-функции
  • в store-действия
  • в сервисные слои (API-модули)
export async function fetchPosts() {
  const res = await fetch('/api/posts')
  return res.json()
}

Такой подход упрощает тестирование, повторное использование и контроль побочных эффектов.


Ключевые принципы работы с асинхронностью во Vue

  • реактивные источники обновляются после завершения асинхронных операций
  • async/await полностью поддерживается на всех уровнях
  • управление загрузкой и ошибками — ответственность компонента или хранилища
  • отмена операций предотвращает утечки памяти и гонки запросов
  • <Suspense> и асинхронные компоненты дают контроль над временем рендера