fix(nuxt): use shared state for asyncData (#7055)

Co-authored-by: Ohb00 <43827372+OhB00@users.noreply.github.com>
This commit is contained in:
pooya parsa 2022-08-30 12:34:09 +02:00 committed by GitHub
parent aece2cd3c2
commit 5a17458af5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 157 additions and 6 deletions

View File

@ -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
data: ref(useInitialCache() ? nuxt.payload.data[key] : options.default?.() ?? null), if (!nuxt._asyncData[key]) {
pending: ref(!useInitialCache()), nuxt._asyncData[key] = {
error: ref(nuxt.payload._errors[key] ?? null) data: ref(useInitialCache() ? nuxt.payload.data[key] : options.default?.() ?? null),
} as AsyncData<DataT, DataE> pending: ref(!useInitialCache()),
error: ref(nuxt.payload._errors[key] ?? null)
}
}
// 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

View File

@ -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

View File

@ -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')
})
})

View 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')

View 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>

View 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>

View 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>

View 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>

View File

@ -0,0 +1,3 @@
let counter = 0
export default () => ({ count: counter++ })