mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-21 21:25:11 +00:00
feat(nuxt): return status
from useAsyncData
(#21045)
This commit is contained in:
parent
0505c9147d
commit
c884a95f0f
@ -38,11 +38,14 @@ type AsyncData<DataT, ErrorT> = {
|
||||
refresh: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
||||
execute: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
||||
error: Ref<ErrorT | null>
|
||||
status: Ref<AsyncDataRequestStatus>
|
||||
};
|
||||
|
||||
interface AsyncDataExecuteOptions {
|
||||
dedupe?: boolean
|
||||
}
|
||||
|
||||
type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
|
||||
```
|
||||
|
||||
## Params
|
||||
@ -66,6 +69,7 @@ Under the hood, `lazy: false` uses `<Suspense>` to block the loading of the rout
|
||||
* **pending**: a boolean indicating whether the data is still being fetched
|
||||
* **refresh**/**execute**: a function that can be used to refresh the data returned by the `handler` function
|
||||
* **error**: an error object if the data fetching failed
|
||||
* **status**: a string indicating the status of the data request (`"idle"`, `"pending"`, `"success"`, `"error"`)
|
||||
|
||||
By default, Nuxt waits until a `refresh` is finished before it can be executed again.
|
||||
|
||||
|
@ -5,6 +5,8 @@ import { useNuxtApp } from '../nuxt'
|
||||
import { createError } from './error'
|
||||
import { onNuxtReady } from './ready'
|
||||
|
||||
export type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
|
||||
|
||||
export type _Transform<Input = any, Output = any> = (input: Input) => Output
|
||||
|
||||
export type PickFrom<T, K extends Array<string>> = T extends Array<any>
|
||||
@ -60,6 +62,7 @@ export interface _AsyncData<DataT, ErrorT> {
|
||||
refresh: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
||||
execute: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
||||
error: Ref<ErrorT | null>
|
||||
status: Ref<AsyncDataRequestStatus>
|
||||
}
|
||||
|
||||
export type AsyncData<Data, Error> = _AsyncData<Data, Error> & Promise<_AsyncData<Data, Error>>
|
||||
@ -125,7 +128,8 @@ export function useAsyncData<
|
||||
nuxt._asyncData[key] = {
|
||||
data: ref(getCachedData() ?? options.default!()),
|
||||
pending: ref(!hasCachedData()),
|
||||
error: toRef(nuxt.payload._errors, key)
|
||||
error: toRef(nuxt.payload._errors, key),
|
||||
status: ref('idle')
|
||||
}
|
||||
}
|
||||
// TODO: Else, somehow check for conflicting keys with different defaults or fetcher
|
||||
@ -144,6 +148,7 @@ export function useAsyncData<
|
||||
return getCachedData()
|
||||
}
|
||||
asyncData.pending.value = true
|
||||
asyncData.status.value = 'pending'
|
||||
// TODO: Cancel previous promise
|
||||
const promise = new Promise<ResT>(
|
||||
(resolve, reject) => {
|
||||
@ -166,6 +171,7 @@ export function useAsyncData<
|
||||
}
|
||||
asyncData.data.value = result
|
||||
asyncData.error.value = null
|
||||
asyncData.status.value = 'success'
|
||||
})
|
||||
.catch((error: any) => {
|
||||
// If this request is cancelled, resolve to the latest request.
|
||||
@ -173,6 +179,7 @@ export function useAsyncData<
|
||||
|
||||
asyncData.error.value = error
|
||||
asyncData.data.value = unref(options.default!())
|
||||
asyncData.status.value = 'error'
|
||||
})
|
||||
.finally(() => {
|
||||
if ((promise as any).cancelled) { return }
|
||||
@ -222,6 +229,7 @@ export function useAsyncData<
|
||||
if (fetchOnServer && nuxt.isHydrating && hasCachedData()) {
|
||||
// 1. Hydration (server: true): no fetch
|
||||
asyncData.pending.value = false
|
||||
asyncData.status.value = asyncData.error.value ? 'error' : 'success'
|
||||
} else if (instance && ((nuxt.payload.serverRendered && nuxt.isHydrating) || options.lazy) && options.immediate) {
|
||||
// 2. Initial load (server: false): fetch on mounted
|
||||
// 3. Initial load or navigation (lazy: true): fetch on mounted
|
||||
@ -328,6 +336,7 @@ export function clearNuxtData (keys?: string | string[] | ((key: string) => bool
|
||||
nuxtApp._asyncData[key]!.data.value = undefined
|
||||
nuxtApp._asyncData[key]!.error.value = undefined
|
||||
nuxtApp._asyncData[key]!.pending.value = false
|
||||
nuxtApp._asyncData[key]!.status.value = 'idle'
|
||||
}
|
||||
if (key in nuxtApp._asyncDataPromises) {
|
||||
nuxtApp._asyncDataPromises[key] = undefined
|
||||
|
@ -14,6 +14,7 @@ import type { RenderResponse } from 'nitropack'
|
||||
import type { NuxtIslandContext } from '../core/runtime/nitro/renderer'
|
||||
import type { RouteMiddleware } from '../../app'
|
||||
import type { NuxtError } from '../app/composables/error'
|
||||
import type { AsyncDataRequestStatus } from '../app/composables/asyncData'
|
||||
|
||||
const nuxtAppCtx = /* #__PURE__ */ getContext<NuxtApp>('nuxt-app')
|
||||
|
||||
@ -87,6 +88,7 @@ interface _NuxtApp {
|
||||
data: Ref<any>
|
||||
pending: Ref<boolean>
|
||||
error: Ref<any>
|
||||
status: Ref<AsyncDataRequestStatus>
|
||||
} | undefined>
|
||||
|
||||
/** @internal */
|
||||
|
@ -1562,6 +1562,19 @@ describe.skipIf(isWindows)('useAsyncData', () => {
|
||||
it('two requests made at once resolve and sync', async () => {
|
||||
await expectNoClientErrors('/useAsyncData/promise-all')
|
||||
})
|
||||
|
||||
it('requests status can be used', async () => {
|
||||
const html = await $fetch('/useAsyncData/status')
|
||||
expect(html).toContain('true')
|
||||
expect(html).not.toContain('false')
|
||||
|
||||
const page = await createPage('/useAsyncData/status')
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
expect(await page.locator('#status5-values').textContent()).toContain('idle,pending,success')
|
||||
|
||||
await page.close()
|
||||
})
|
||||
})
|
||||
|
||||
describe.runIf(isDev())('component testing', () => {
|
||||
|
49
test/fixtures/basic/pages/useAsyncData/status.vue
vendored
Normal file
49
test/fixtures/basic/pages/useAsyncData/status.vue
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
<script setup lang="ts">
|
||||
const { status: status1 } = await useAsyncData(() => Promise.resolve(true))
|
||||
if (status1.value !== 'success') {
|
||||
throw new Error('status1 should be "success"')
|
||||
}
|
||||
|
||||
const { status: status2 } = await useAsyncData(() => Promise.reject(Error('boom!')))
|
||||
if (status2.value !== 'error') {
|
||||
throw new Error('status2 should be "error"')
|
||||
}
|
||||
|
||||
const { status: status3 } = await useAsyncData(() => Promise.resolve(true), { immediate: false })
|
||||
if (status3.value !== 'idle') {
|
||||
throw new Error('status3 should be "idle"')
|
||||
}
|
||||
|
||||
const { status: status4, execute } = await useAsyncData(() => Promise.resolve(true), { immediate: false })
|
||||
await execute()
|
||||
if (status4.value !== 'success') {
|
||||
throw new Error('status4 should be "success"')
|
||||
}
|
||||
|
||||
const { status: status5 } = await useAsyncData(() => Promise.resolve(true), { server: false })
|
||||
if (process.server && status5.value !== 'idle') {
|
||||
throw new Error('status5 should be "idle" server side')
|
||||
}
|
||||
|
||||
const status5Values = ref<string[]>([])
|
||||
watchEffect(() => {
|
||||
status5Values.value.push(status5.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
Status
|
||||
<div>
|
||||
{{ status1 === 'success' }}
|
||||
{{ status2 === 'error' }}
|
||||
{{ status3 === 'idle' }}
|
||||
{{ status4 === 'success' }}
|
||||
<ClientOnly>
|
||||
<div id="status5-values">
|
||||
{{ status5Values.join(',') }}
|
||||
</div>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
Loading…
Reference in New Issue
Block a user