mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-21 21:25:11 +00:00
feat(nuxt): add dedupe
option for data fetching composables (#24564)
This commit is contained in:
parent
17b5ed9ad8
commit
8ccafb182d
@ -64,6 +64,9 @@ const { data: posts } = await useAsyncData(
|
||||
- `pick`: only pick specified keys in this array from the `handler` function result
|
||||
- `watch`: watch reactive sources to auto-refresh
|
||||
- `deep`: return data in a deep ref object (it is `true` by default). It can be set to `false` to return data in a shallow ref object, which can improve performance if your data does not need to be deeply reactive.
|
||||
- `dedupe`: avoid fetching same key more than once at a time (defaults to `cancel`). Possible options:
|
||||
- `cancel` - cancels existing requests when a new one is made
|
||||
- `defer` - does not make new requests at all if there is a pending request
|
||||
|
||||
::callout
|
||||
Under the hood, `lazy: false` uses `<Suspense>` to block the loading of the route before the data has been fetched. Consider using `lazy: true` and implementing a loading state instead for a snappier user experience.
|
||||
@ -105,6 +108,7 @@ type AsyncDataOptions<DataT> = {
|
||||
lazy?: boolean
|
||||
immediate?: boolean
|
||||
deep?: boolean
|
||||
dedupe?: 'cancel' | 'defer'
|
||||
default?: () => DataT | Ref<DataT> | null
|
||||
transform?: (input: DataT) => DataT
|
||||
pick?: string[]
|
||||
@ -122,7 +126,7 @@ type AsyncData<DataT, ErrorT> = {
|
||||
};
|
||||
|
||||
interface AsyncDataExecuteOptions {
|
||||
dedupe?: boolean
|
||||
dedupe?: 'cancel' | 'defer'
|
||||
}
|
||||
|
||||
type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
|
||||
|
@ -96,6 +96,9 @@ All fetch options can be given a `computed` or `ref` value. These will be watche
|
||||
- `pick`: only pick specified keys in this array from the `handler` function result
|
||||
- `watch`: watch an array of reactive sources and auto-refresh the fetch result when they change. Fetch options and URL are watched by default. You can completely ignore reactive sources by using `watch: false`. Together with `immediate: false`, this allows for a fully-manual `useFetch`.
|
||||
- `deep`: return data in a deep ref object (it is `true` by default). It can be set to `false` to return data in a shallow ref object, which can improve performance if your data does not need to be deeply reactive.
|
||||
- `dedupe`: avoid fetching same key more than once at a time (defaults to `cancel`). Possible options:
|
||||
- `cancel` - cancels existing requests when a new one is made
|
||||
- `defer` - does not make new requests at all if there is a pending request
|
||||
|
||||
::callout
|
||||
If you provide a function or ref as the `url` parameter, or if you provide functions as arguments to the `options` parameter, then the `useFetch` call will not match other `useFetch` calls elsewhere in your codebase, even if the options seem to be identical. If you wish to force a match, you may provide your own key in `options`.
|
||||
@ -136,6 +139,7 @@ type UseFetchOptions<DataT> = {
|
||||
immediate?: boolean
|
||||
getCachedData?: (key: string) => DataT
|
||||
deep?: boolean
|
||||
dedupe?: 'cancel' | 'defer'
|
||||
default?: () => DataT
|
||||
transform?: (input: DataT) => DataT
|
||||
pick?: string[]
|
||||
@ -152,7 +156,7 @@ type AsyncData<DataT, ErrorT> = {
|
||||
}
|
||||
|
||||
interface AsyncDataExecuteOptions {
|
||||
dedupe?: boolean
|
||||
dedupe?: 'cancel' | 'defer'
|
||||
}
|
||||
|
||||
type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
|
||||
|
@ -49,16 +49,21 @@ export interface AsyncDataOptions<
|
||||
watch?: MultiWatchSources
|
||||
immediate?: boolean
|
||||
deep?: boolean
|
||||
dedupe?: 'cancel' | 'defer'
|
||||
}
|
||||
|
||||
export interface AsyncDataExecuteOptions {
|
||||
_initial?: boolean
|
||||
// TODO: deprecate boolean option in future minor
|
||||
/**
|
||||
* Force a refresh, even if there is already a pending request. Previous requests will
|
||||
* not be cancelled, but their result will not affect the data/pending state - and any
|
||||
* previously awaited promises will not resolve until this new request resolves.
|
||||
*
|
||||
* Instead of using `boolean` values, use `cancel` for `true` and `defer` for `false`.
|
||||
* Boolean values will be removed in a future release.
|
||||
*/
|
||||
dedupe?: boolean
|
||||
dedupe?: boolean | 'cancel' | 'defer'
|
||||
}
|
||||
|
||||
export interface _AsyncData<DataT, ErrorT> {
|
||||
@ -72,6 +77,9 @@ export interface _AsyncData<DataT, ErrorT> {
|
||||
|
||||
export type AsyncData<Data, Error> = _AsyncData<Data, Error> & Promise<_AsyncData<Data, Error>>
|
||||
|
||||
// TODO: deprecate boolean option in future minor
|
||||
const isDefer = (dedupe?: boolean | 'cancel' | 'defer') => dedupe === 'defer' || dedupe === false
|
||||
|
||||
export function useAsyncData<
|
||||
ResT,
|
||||
DataE = Error,
|
||||
@ -150,6 +158,7 @@ export function useAsyncData<
|
||||
options.lazy = options.lazy ?? false
|
||||
options.immediate = options.immediate ?? true
|
||||
options.deep = options.deep ?? asyncDataDefaults.deep
|
||||
options.dedupe = options.dedupe ?? 'cancel'
|
||||
|
||||
const hasCachedData = () => ![null, undefined].includes(options.getCachedData!(key) as any)
|
||||
|
||||
@ -172,7 +181,7 @@ export function useAsyncData<
|
||||
|
||||
asyncData.refresh = asyncData.execute = (opts = {}) => {
|
||||
if (nuxt._asyncDataPromises[key]) {
|
||||
if (opts.dedupe === false) {
|
||||
if (isDefer(opts.dedupe ?? options.dedupe)) {
|
||||
// Avoid fetching same key more than once at a time
|
||||
return nuxt._asyncDataPromises[key]!
|
||||
}
|
||||
|
@ -239,6 +239,33 @@ describe('useAsyncData', () => {
|
||||
const { data } = await useAsyncData(() => Promise.reject(new Error('test')), { default: () => 'default' })
|
||||
expect(data.value).toMatchInlineSnapshot('"default"')
|
||||
})
|
||||
|
||||
it('should execute the promise function once when dedupe option is "defer" for multiple calls', async () => {
|
||||
const promiseFn = vi.fn(() => Promise.resolve('test'))
|
||||
useAsyncData('dedupedKey', promiseFn, { dedupe: 'defer' })
|
||||
useAsyncData('dedupedKey', promiseFn, { dedupe: 'defer' })
|
||||
useAsyncData('dedupedKey', promiseFn, { dedupe: 'defer' })
|
||||
|
||||
expect(promiseFn).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should execute the promise function multiple times when dedupe option is not specified for multiple calls', async () => {
|
||||
const promiseFn = vi.fn(() => Promise.resolve('test'))
|
||||
useAsyncData('dedupedKey', promiseFn)
|
||||
useAsyncData('dedupedKey', promiseFn)
|
||||
useAsyncData('dedupedKey', promiseFn)
|
||||
|
||||
expect(promiseFn).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
it('should execute the promise function as per dedupe option when different dedupe options are used for multiple calls', async () => {
|
||||
const promiseFn = vi.fn(() => Promise.resolve('test'))
|
||||
useAsyncData('dedupedKey', promiseFn, { dedupe: 'defer' })
|
||||
useAsyncData('dedupedKey', promiseFn)
|
||||
useAsyncData('dedupedKey', promiseFn, { dedupe: 'defer' })
|
||||
|
||||
expect(promiseFn).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('useFetch', () => {
|
||||
|
Loading…
Reference in New Issue
Block a user