mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +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>
|
refresh: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
||||||
execute: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
execute: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
||||||
error: Ref<ErrorT | null>
|
error: Ref<ErrorT | null>
|
||||||
|
status: Ref<AsyncDataRequestStatus>
|
||||||
};
|
};
|
||||||
|
|
||||||
interface AsyncDataExecuteOptions {
|
interface AsyncDataExecuteOptions {
|
||||||
dedupe?: boolean
|
dedupe?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Params
|
## 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
|
* **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
|
* **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
|
* **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.
|
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 { createError } from './error'
|
||||||
import { onNuxtReady } from './ready'
|
import { onNuxtReady } from './ready'
|
||||||
|
|
||||||
|
export type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
|
||||||
|
|
||||||
export type _Transform<Input = any, Output = any> = (input: Input) => Output
|
export type _Transform<Input = any, Output = any> = (input: Input) => Output
|
||||||
|
|
||||||
export type PickFrom<T, K extends Array<string>> = T extends Array<any>
|
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>
|
refresh: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
||||||
execute: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
execute: (opts?: AsyncDataExecuteOptions) => Promise<void>
|
||||||
error: Ref<ErrorT | null>
|
error: Ref<ErrorT | null>
|
||||||
|
status: Ref<AsyncDataRequestStatus>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AsyncData<Data, Error> = _AsyncData<Data, Error> & Promise<_AsyncData<Data, Error>>
|
export type AsyncData<Data, Error> = _AsyncData<Data, Error> & Promise<_AsyncData<Data, Error>>
|
||||||
@ -125,7 +128,8 @@ export function useAsyncData<
|
|||||||
nuxt._asyncData[key] = {
|
nuxt._asyncData[key] = {
|
||||||
data: ref(getCachedData() ?? options.default!()),
|
data: ref(getCachedData() ?? options.default!()),
|
||||||
pending: ref(!hasCachedData()),
|
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
|
// TODO: Else, somehow check for conflicting keys with different defaults or fetcher
|
||||||
@ -144,6 +148,7 @@ export function useAsyncData<
|
|||||||
return getCachedData()
|
return getCachedData()
|
||||||
}
|
}
|
||||||
asyncData.pending.value = true
|
asyncData.pending.value = true
|
||||||
|
asyncData.status.value = 'pending'
|
||||||
// TODO: Cancel previous promise
|
// TODO: Cancel previous promise
|
||||||
const promise = new Promise<ResT>(
|
const promise = new Promise<ResT>(
|
||||||
(resolve, reject) => {
|
(resolve, reject) => {
|
||||||
@ -166,6 +171,7 @@ export function useAsyncData<
|
|||||||
}
|
}
|
||||||
asyncData.data.value = result
|
asyncData.data.value = result
|
||||||
asyncData.error.value = null
|
asyncData.error.value = null
|
||||||
|
asyncData.status.value = 'success'
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
// If this request is cancelled, resolve to the latest request.
|
// If this request is cancelled, resolve to the latest request.
|
||||||
@ -173,6 +179,7 @@ export function useAsyncData<
|
|||||||
|
|
||||||
asyncData.error.value = error
|
asyncData.error.value = error
|
||||||
asyncData.data.value = unref(options.default!())
|
asyncData.data.value = unref(options.default!())
|
||||||
|
asyncData.status.value = 'error'
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
if ((promise as any).cancelled) { return }
|
if ((promise as any).cancelled) { return }
|
||||||
@ -222,6 +229,7 @@ export function useAsyncData<
|
|||||||
if (fetchOnServer && nuxt.isHydrating && hasCachedData()) {
|
if (fetchOnServer && nuxt.isHydrating && hasCachedData()) {
|
||||||
// 1. Hydration (server: true): no fetch
|
// 1. Hydration (server: true): no fetch
|
||||||
asyncData.pending.value = false
|
asyncData.pending.value = false
|
||||||
|
asyncData.status.value = asyncData.error.value ? 'error' : 'success'
|
||||||
} else if (instance && ((nuxt.payload.serverRendered && nuxt.isHydrating) || options.lazy) && options.immediate) {
|
} else if (instance && ((nuxt.payload.serverRendered && nuxt.isHydrating) || options.lazy) && options.immediate) {
|
||||||
// 2. Initial load (server: false): fetch on mounted
|
// 2. Initial load (server: false): fetch on mounted
|
||||||
// 3. Initial load or navigation (lazy: true): 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]!.data.value = undefined
|
||||||
nuxtApp._asyncData[key]!.error.value = undefined
|
nuxtApp._asyncData[key]!.error.value = undefined
|
||||||
nuxtApp._asyncData[key]!.pending.value = false
|
nuxtApp._asyncData[key]!.pending.value = false
|
||||||
|
nuxtApp._asyncData[key]!.status.value = 'idle'
|
||||||
}
|
}
|
||||||
if (key in nuxtApp._asyncDataPromises) {
|
if (key in nuxtApp._asyncDataPromises) {
|
||||||
nuxtApp._asyncDataPromises[key] = undefined
|
nuxtApp._asyncDataPromises[key] = undefined
|
||||||
|
@ -14,6 +14,7 @@ import type { RenderResponse } from 'nitropack'
|
|||||||
import type { NuxtIslandContext } from '../core/runtime/nitro/renderer'
|
import type { NuxtIslandContext } from '../core/runtime/nitro/renderer'
|
||||||
import type { RouteMiddleware } from '../../app'
|
import type { RouteMiddleware } from '../../app'
|
||||||
import type { NuxtError } from '../app/composables/error'
|
import type { NuxtError } from '../app/composables/error'
|
||||||
|
import type { AsyncDataRequestStatus } from '../app/composables/asyncData'
|
||||||
|
|
||||||
const nuxtAppCtx = /* #__PURE__ */ getContext<NuxtApp>('nuxt-app')
|
const nuxtAppCtx = /* #__PURE__ */ getContext<NuxtApp>('nuxt-app')
|
||||||
|
|
||||||
@ -87,6 +88,7 @@ interface _NuxtApp {
|
|||||||
data: Ref<any>
|
data: Ref<any>
|
||||||
pending: Ref<boolean>
|
pending: Ref<boolean>
|
||||||
error: Ref<any>
|
error: Ref<any>
|
||||||
|
status: Ref<AsyncDataRequestStatus>
|
||||||
} | undefined>
|
} | undefined>
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
|
@ -1562,6 +1562,19 @@ describe.skipIf(isWindows)('useAsyncData', () => {
|
|||||||
it('two requests made at once resolve and sync', async () => {
|
it('two requests made at once resolve and sync', async () => {
|
||||||
await expectNoClientErrors('/useAsyncData/promise-all')
|
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', () => {
|
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