From dc9d756e5bb1f14a304b263f7aede217b91011bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 13 Aug 2024 21:24:32 +0200 Subject: [PATCH] perf(nuxt): avoid multiple calls to `getCachedData` (#28472) --- .../nuxt/src/app/composables/asyncData.ts | 20 ++++++++++--------- test/nuxt/composables.test.ts | 11 ++++++++++ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/nuxt/src/app/composables/asyncData.ts b/packages/nuxt/src/app/composables/asyncData.ts index 86006ff822..8f491750e8 100644 --- a/packages/nuxt/src/app/composables/asyncData.ts +++ b/packages/nuxt/src/app/composables/asyncData.ts @@ -239,18 +239,17 @@ export function useAsyncData< options.deep = options.deep ?? asyncDataDefaults.deep options.dedupe = options.dedupe ?? 'cancel' - // TODO: make more precise when v4 lands - const hasCachedData = () => options.getCachedData!(key, nuxtApp) !== undefined - // Create or use a shared asyncData entity + const initialCachedData = options.getCachedData!(key, nuxtApp) + const hasCachedData = typeof initialCachedData !== 'undefined' + if (!nuxtApp._asyncData[key] || !options.immediate) { nuxtApp.payload._errors[key] ??= undefined const _ref = options.deep ? ref : shallowRef - const cachedData = options.getCachedData!(key, nuxtApp) nuxtApp._asyncData[key] = { - data: _ref(typeof cachedData !== 'undefined' ? cachedData : options.default!()), - pending: ref(!hasCachedData()), + data: _ref(hasCachedData ? initialCachedData : options.default!()), + pending: ref(!hasCachedData), error: toRef(nuxtApp.payload._errors, key), status: ref('idle'), _default: options.default!, @@ -272,8 +271,11 @@ export function useAsyncData< (nuxtApp._asyncDataPromises[key] as any).cancelled = true } // Avoid fetching same key that is already fetched - if ((opts._initial || (nuxtApp.isHydrating && opts._initial !== false)) && hasCachedData()) { - return Promise.resolve(options.getCachedData!(key, nuxtApp)) + if ((opts._initial || (nuxtApp.isHydrating && opts._initial !== false))) { + const cachedData = opts._initial ? initialCachedData : options.getCachedData!(key, nuxtApp) + if (typeof cachedData !== 'undefined') { + return Promise.resolve(cachedData) + } } asyncData.pending.value = true asyncData.status.value = 'pending' @@ -362,7 +364,7 @@ export function useAsyncData< onUnmounted(() => cbs.splice(0, cbs.length)) } - if (fetchOnServer && nuxtApp.isHydrating && (asyncData.error.value || hasCachedData())) { + if (fetchOnServer && nuxtApp.isHydrating && (asyncData.error.value || typeof initialCachedData !== 'undefined')) { // 1. Hydration (server: true): no fetch asyncData.pending.value = false asyncData.status.value = asyncData.error.value ? 'error' : 'success' diff --git a/test/nuxt/composables.test.ts b/test/nuxt/composables.test.ts index 9fcb97dedd..d308e3cb07 100644 --- a/test/nuxt/composables.test.ts +++ b/test/nuxt/composables.test.ts @@ -224,6 +224,17 @@ describe('useAsyncData', () => { `) }) + it('should only call getCachedData once', async () => { + const getCachedData = vi.fn(() => ({ val: false })) + const { data } = await useAsyncData(() => Promise.resolve({ val: true }), { getCachedData }) + expect(data.value).toMatchInlineSnapshot(` + { + "val": false, + } + `) + expect(getCachedData).toHaveBeenCalledTimes(1) + }) + it('should use default while pending', async () => { const promise = useAsyncData(() => Promise.resolve('test'), { default: () => 'default' }) const { data, pending } = promise