From b13582205abf177fc315b1592c31acde433c2b61 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 097e05f087..f473871e7e 100644 --- a/packages/nuxt/src/app/composables/asyncData.ts +++ b/packages/nuxt/src/app/composables/asyncData.ts @@ -252,18 +252,17 @@ export function useAsyncData< console.warn('[nuxt] `boolean` values are deprecated for the `dedupe` option of `useAsyncData` and will be removed in the future. Use \'cancel\' or \'defer\' instead.') } - // TODO: make more precise when v4 lands - const hasCachedData = () => options.getCachedData!(key, nuxtApp) != null - // Create or use a shared asyncData entity + const initialCachedData = options.getCachedData!(key, nuxtApp) + const hasCachedData = initialCachedData != null + if (!nuxtApp._asyncData[key] || !options.immediate) { nuxtApp.payload._errors[key] ??= asyncDataDefaults.errorValue const _ref = options.deep ? ref : shallowRef - nuxtApp._asyncData[key] = { - data: _ref(options.getCachedData!(key, nuxtApp) ?? 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!, @@ -285,8 +284,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 (cachedData != null) { + return Promise.resolve(cachedData) + } } asyncData.pending.value = true asyncData.status.value = 'pending' @@ -375,7 +377,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 || initialCachedData != null)) { // 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 c707f8be2d..bd8865d028 100644 --- a/test/nuxt/composables.test.ts +++ b/test/nuxt/composables.test.ts @@ -227,6 +227,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