mirror of
https://github.com/nuxt/nuxt.git
synced 2024-12-01 18:07:22 +00:00
fix(nuxt): use shared state for asyncData (#7055)
Co-authored-by: Ohb00 <43827372+OhB00@users.noreply.github.com>
This commit is contained in:
parent
aece2cd3c2
commit
5a17458af5
@ -114,11 +114,16 @@ export function useAsyncData<
|
|||||||
|
|
||||||
const useInitialCache = () => (nuxt.isHydrating || options.initialCache) && nuxt.payload.data[key] !== undefined
|
const useInitialCache = () => (nuxt.isHydrating || options.initialCache) && nuxt.payload.data[key] !== undefined
|
||||||
|
|
||||||
const asyncData = {
|
// Create or use a shared asyncData entity
|
||||||
|
if (!nuxt._asyncData[key]) {
|
||||||
|
nuxt._asyncData[key] = {
|
||||||
data: ref(useInitialCache() ? nuxt.payload.data[key] : options.default?.() ?? null),
|
data: ref(useInitialCache() ? nuxt.payload.data[key] : options.default?.() ?? null),
|
||||||
pending: ref(!useInitialCache()),
|
pending: ref(!useInitialCache()),
|
||||||
error: ref(nuxt.payload._errors[key] ?? null)
|
error: ref(nuxt.payload._errors[key] ?? null)
|
||||||
} as AsyncData<DataT, DataE>
|
}
|
||||||
|
}
|
||||||
|
// TODO: Else, Soemhow check for confliciting keys with different defaults or fetcher
|
||||||
|
const asyncData = { ...nuxt._asyncData[key] } as AsyncData<DataT, DataE>
|
||||||
|
|
||||||
asyncData.refresh = (opts = {}) => {
|
asyncData.refresh = (opts = {}) => {
|
||||||
// Avoid fetching same key more than once at a time
|
// Avoid fetching same key more than once at a time
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable no-use-before-define */
|
/* eslint-disable no-use-before-define */
|
||||||
import { getCurrentInstance, reactive } from 'vue'
|
import { getCurrentInstance, reactive, Ref } from 'vue'
|
||||||
import type { App, onErrorCaptured, VNode } from 'vue'
|
import type { App, onErrorCaptured, VNode } from 'vue'
|
||||||
import { createHooks, Hookable } from 'hookable'
|
import { createHooks, Hookable } from 'hookable'
|
||||||
import type { RuntimeConfig, AppConfigInput } from '@nuxt/schema'
|
import type { RuntimeConfig, AppConfigInput } from '@nuxt/schema'
|
||||||
@ -66,6 +66,11 @@ interface _NuxtApp {
|
|||||||
[key: string]: any
|
[key: string]: any
|
||||||
|
|
||||||
_asyncDataPromises: Record<string, Promise<any> | undefined>
|
_asyncDataPromises: Record<string, Promise<any> | undefined>
|
||||||
|
_asyncData: Record<string, {
|
||||||
|
data: Ref<any>
|
||||||
|
pending: Ref<boolean>
|
||||||
|
error: Ref<any>
|
||||||
|
}>,
|
||||||
|
|
||||||
ssrContext?: NuxtSSRContext
|
ssrContext?: NuxtSSRContext
|
||||||
payload: {
|
payload: {
|
||||||
@ -113,6 +118,7 @@ export function createNuxtApp (options: CreateOptions) {
|
|||||||
}),
|
}),
|
||||||
isHydrating: process.client,
|
isHydrating: process.client,
|
||||||
_asyncDataPromises: {},
|
_asyncDataPromises: {},
|
||||||
|
_asyncData: {},
|
||||||
...options
|
...options
|
||||||
} as any as NuxtApp
|
} as any as NuxtApp
|
||||||
|
|
||||||
|
@ -527,3 +527,21 @@ describe('app config', () => {
|
|||||||
expect(html).toContain(JSON.stringify(expectedAppConfig))
|
expect(html).toContain(JSON.stringify(expectedAppConfig))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('useAsyncData', () => {
|
||||||
|
it('single request resolves', async () => {
|
||||||
|
await expectNoClientErrors('/useAsyncData/single')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('two requests resolve', async () => {
|
||||||
|
await expectNoClientErrors('/useAsyncData/double')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('two requests resolve and sync', async () => {
|
||||||
|
await $fetch('/useAsyncData/refresh')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('two requests made at once resolve and sync', async () => {
|
||||||
|
await expectNoClientErrors('/useAsyncData/promise-all')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
7
test/fixtures/basic/composables/asyncDataTests.ts
vendored
Normal file
7
test/fixtures/basic/composables/asyncDataTests.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export const useSleep = () => useAsyncData('sleep', async () => {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 50))
|
||||||
|
|
||||||
|
return 'Slept!'
|
||||||
|
})
|
||||||
|
|
||||||
|
export const useCounter = () => useFetch('/api/useAsyncData/count')
|
26
test/fixtures/basic/pages/useAsyncData/double.vue
vendored
Normal file
26
test/fixtures/basic/pages/useAsyncData/double.vue
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
Single
|
||||||
|
<div>
|
||||||
|
data1: {{ data1 }}
|
||||||
|
data2: {{ data2 }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { data: data1 } = await useSleep()
|
||||||
|
const { data: data2 } = await useSleep()
|
||||||
|
|
||||||
|
if (data1.value === null || data1.value === undefined || data1.value.length <= 0) {
|
||||||
|
throw new Error('Data should never be null or empty.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data2.value === null || data2.value === undefined || data2.value.length <= 0) {
|
||||||
|
throw new Error('Data should never be null or empty.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data1.value !== data2.value) {
|
||||||
|
throw new Error('AsyncData not synchronised')
|
||||||
|
}
|
||||||
|
</script>
|
31
test/fixtures/basic/pages/useAsyncData/promise-all.vue
vendored
Normal file
31
test/fixtures/basic/pages/useAsyncData/promise-all.vue
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
Single
|
||||||
|
<div>
|
||||||
|
data1: {{ result1.data.value }}
|
||||||
|
data2: {{ result2.data.value }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const [result1, result2] = await Promise.all([useSleep(), useSleep()])
|
||||||
|
|
||||||
|
if (result1.data.value === null || result1.data.value === undefined || result1.data.value.length <= 0) {
|
||||||
|
throw new Error('Data should never be null or empty.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result2.data.value === null || result2.data.value === undefined || result2.data.value.length <= 0) {
|
||||||
|
throw new Error('Data should never be null or empty.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result1.data.value !== result2.data.value) {
|
||||||
|
throw new Error('AsyncData not synchronised')
|
||||||
|
}
|
||||||
|
|
||||||
|
await result1.refresh()
|
||||||
|
|
||||||
|
if (result1.data.value !== result2.data.value) {
|
||||||
|
throw new Error('AsyncData not synchronised')
|
||||||
|
}
|
||||||
|
</script>
|
39
test/fixtures/basic/pages/useAsyncData/refresh.vue
vendored
Normal file
39
test/fixtures/basic/pages/useAsyncData/refresh.vue
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
Single
|
||||||
|
<div>
|
||||||
|
{{ data }} - {{ data2 }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { data, refresh } = await useCounter()
|
||||||
|
const { data: data2, refresh: refresh2 } = await useCounter()
|
||||||
|
|
||||||
|
let inital = data.value.count
|
||||||
|
|
||||||
|
// Refresh on client and server side
|
||||||
|
await refresh()
|
||||||
|
|
||||||
|
if (data.value.count !== inital + 1) {
|
||||||
|
throw new Error('Data not refreshed?' + data.value.count + ' : ' + data2.value.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.value.count !== data2.value.count) {
|
||||||
|
throw new Error('AsyncData not synchronised')
|
||||||
|
}
|
||||||
|
|
||||||
|
inital = data.value.count
|
||||||
|
|
||||||
|
await refresh2()
|
||||||
|
|
||||||
|
if (data.value.count !== inital + 1) {
|
||||||
|
throw new Error('data2 refresh not syncronised?')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.value.count !== data2.value.count) {
|
||||||
|
throw new Error('AsyncData not synchronised')
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
16
test/fixtures/basic/pages/useAsyncData/single.vue
vendored
Normal file
16
test/fixtures/basic/pages/useAsyncData/single.vue
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
Single
|
||||||
|
<div>
|
||||||
|
{{ data }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { data } = await useSleep()
|
||||||
|
|
||||||
|
if (data.value === null || data.value === undefined || data.value.length <= 0) {
|
||||||
|
throw new Error('Data should never be null or empty.')
|
||||||
|
}
|
||||||
|
</script>
|
3
test/fixtures/basic/server/api/useAsyncData/count.ts
vendored
Normal file
3
test/fixtures/basic/server/api/useAsyncData/count.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
let counter = 0
|
||||||
|
|
||||||
|
export default () => ({ count: counter++ })
|
Loading…
Reference in New Issue
Block a user