Day 13: Handling Async State
Welcome to Day 13! Most apps need to fetch data from a server. Today we look at how to handle the âintermediateâ states: Loading, Error, and Success.
The Standard Pattern
Without any special features, we usually do this:
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const data = ref(null)
const error = ref(null)
const loading = ref(true)
onMounted(async () => {
try {
const res = await fetch('https://api.example.com/data')
data.value = await res.json()
} catch (e) {
error.value = e
} finally {
loading.value = false
}
})
</script>
<template>
<div v-if="loading">Loading...</div>
<div v-else-if="error">Error: {{ error }}</div>
<div v-else>
{{ data }}
</div>
</template>
This works, but itâs repetitive.
Async Setup & Suspense
Vue 3 supports await directly at the top level of <script setup>.
UserProfile.vue
<script setup lang="ts">
const res = await fetch('https://api.example.com/user')
const user = await res.json()
</script>
<template>
<h1>{{ user.name }}</h1>
</template>
If you try to use this component, it wonât render⌠unless you wrap it in a <Suspense> component in the parent.
App.vue
<template>
<Suspense>
<!-- Main content -->
<UserProfile />
<!-- Loading state -->
<template #fallback>
<div>Loading Profile...</div>
</template>
</Suspense>
</template>
Note:
<Suspense>is still technically an experimental feature, but itâs widely used in the ecosystem (Nuxt 3 uses it extensively).
Handling Errors with onErrorCaptured
If an async component fails, <Suspense> doesnât handle the error. You need a parent component to âcatchâ it.
<script setup lang="ts">
import { onErrorCaptured, ref } from 'vue'
const error = ref(null)
onErrorCaptured((err) => {
error.value = err
return false // prevent error from propagating further
})
</script>
<template>
<div v-if="error">Everything is broken: {{ error }}</div>
<Suspense v-else>
<UserProfile />
<template #fallback>Loading...</template>
</Suspense>
</template>
Challenge for Day 13
- Create a component
AsyncQuote. - In
script setup,await fetch('https://dummyjson.com/quotes/random'). - Display the quote.
- In
App.vue, wrap it inSuspense. Show âFetching wisdomâŚâ while loading.
Solution:
AsyncQuote.vue
<script setup lang="ts">
const res = await fetch('https://dummyjson.com/quotes/random')
const data = await res.json()
</script>
<template>
<blockquote>"{{ data.quote }}" - {{ data.author }}</blockquote>
</template>
App.vue
<template>
<h1>Daily Wisdom</h1>
<Suspense>
<AsyncQuote />
<template #fallback>
<p>Fetching wisdom...</p>
</template>
</Suspense>
</template>
Tomorrow is the big day. We combine Router, Pinia, and Async into a final project!