Skip to content

Conversation

@OrbisK
Copy link
Member

@OrbisK OrbisK commented Nov 20, 2025

🔗 Linked issue

fixes #33737

📚 Description

My approach is to always return the new deduped promise for useAsyncData executes, if the promise is rejected from deduplication. This causes all executions to resolve simultaneously because they are the 'same' promise, and I believe this is the intended behaviour. If I want to perform some actions after execution, I want to do so after the new promise has been resolved, rather than when the old one has been deduplicated.

execute().then(()=>console.log(1)) 
execute().then(()=>console.log(2))
execute().then(()=>console.log(3)) 
// all resolve at the same time after the last execute is resolved

@bolt-new-by-stackblitz
Copy link

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@OrbisK OrbisK changed the title fix(nuxt): return promise on dedupe/cancel fix(nuxt): always return promise on async-data deduplication Nov 20, 2025
@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 20, 2025

Open in StackBlitz

@nuxt/kit

npm i https://pkg.pr.new/@nuxt/kit@33740

@nuxt/nitro-server

npm i https://pkg.pr.new/@nuxt/nitro-server@33740

nuxt

npm i https://pkg.pr.new/nuxt@33740

@nuxt/rspack-builder

npm i https://pkg.pr.new/@nuxt/rspack-builder@33740

@nuxt/schema

npm i https://pkg.pr.new/@nuxt/schema@33740

@nuxt/vite-builder

npm i https://pkg.pr.new/@nuxt/vite-builder@33740

@nuxt/webpack-builder

npm i https://pkg.pr.new/@nuxt/webpack-builder@33740

commit: c763276

@codspeed-hq
Copy link

codspeed-hq bot commented Nov 20, 2025

CodSpeed Performance Report

Merging #33740 will not alter performance

Comparing OrbisK:fix/async-data-cancel-server (c763276) with main (ec316ea)

Summary

✅ 10 untouched

@OrbisK OrbisK marked this pull request as ready for review November 20, 2025 18:19
@OrbisK OrbisK requested a review from danielroe as a code owner November 20, 2025 18:19
@coderabbitai
Copy link

coderabbitai bot commented Nov 20, 2025

Walkthrough

This pull request modifies error handling in the async data composable by updating three catch block branches to return the existing per-key promise rather than undefined. These changes affect scenarios where the current promise is replaced, where asyncData is aborted internally via dedupe or clear, and where an AbortError occurs. Accompanying this, a new test case is added to verify that multiple concurrent useAsyncData calls with the same key properly resolve to the same result when deduplication is enabled.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

  • The changes follow a consistent, repetitive pattern across three error handling branches in a single file
  • The modification logic is straightforward: returning an existing promise instead of undefined
  • Key areas requiring attention:
    • Verification that returning the per-key promise in each error scenario produces the correct behavioural outcome
    • Confirmation that the new test case accurately validates deduped promise resolution timing and counts
    • Assessment of potential side effects on promise handling in downstream code

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: fixing async-data executes to return the new deduplicated promise instead of undefined.
Description check ✅ Passed The description is directly related to the changeset, explaining the rationale for returning deduped promises and providing a code example of the intended behaviour.
Linked Issues check ✅ Passed The PR directly addresses issue #33737 by ensuring that multiple concurrent useAsyncData calls with the same key return the deduped promise, resolving the undefined data issue in SSR scenarios.
Out of Scope Changes check ✅ Passed All changes are in-scope: the asyncData.ts modifications fix the deduplication promise return behaviour, and the test addition validates that deduped promises resolve simultaneously.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/nuxt/src/app/composables/asyncData.ts (1)

749-769: Lines 757 and 763 require guards to prevent returning undefined promises.

The first catch branch (line 751–752) safely guards against undefined with if (nuxtApp._asyncDataPromises[key] && nuxtApp._asyncDataPromises[key] !== promise), but lines 757 and 763 return nuxtApp._asyncDataPromises[key] directly without guarding.

If clearNuxtData executes between the promise assignment (line 778) and the catch block, it sets the key to undefined (line 625), causing lines 757 and 763 to return undefined instead of a valid promise. This breaks the promise chain and contradicts the guard logic applied at line 751.

Fix: Apply the same existence check at lines 757 and 763:

  • Line 757: if (nuxtApp._asyncDataPromises[key]) { return nuxtApp._asyncDataPromises[key] }
  • Line 763: if (nuxtApp._asyncDataPromises[key]) { return nuxtApp._asyncDataPromises[key] }

Alternatively, return the fallback promise variable instead of the cache lookup.

🧹 Nitpick comments (2)
test/nuxt/use-async-data.test.ts (1)

1157-1173: Clarify the expected behaviour with dedupe: 'cancel'.

The test expects promiseFn to be called 4 times with dedupe: 'cancel'. This is correct because each new call cancels the previous request and starts a fresh one. However, this differs from the objective of "deduplication" described in the PR.

The test validates that even though earlier promises are cancelled, they still resolve correctly (by returning the per-key promise) rather than remaining unresolved or returning undefined. Consider adding a comment explaining this behaviour to distinguish it from dedupe: 'defer' (line 362) where the function is called only once.

  it('should correctly return deduped promises', async () => {
    vi.useFakeTimers()
    const promiseFn = vi.fn(() => new Promise(resolve => setTimeout(() => resolve('test'), 100)))

+   // With dedupe: 'cancel', each call cancels the previous one, but all promises
+   // should still resolve by returning the current per-key promise
    const p1 = useAsyncData('sameKey', promiseFn, { dedupe: 'cancel' })
test/fixtures/basic/app/components/AsyncData.vue (1)

11-14: Consider documenting the hardcoded key usage.

The component uses a hardcoded key 'async-data-component' for useAsyncData, meaning all instances of this component share the same async data. Whilst this is intentional for testing deduplication behaviour, it's an anti-pattern in production code.

Consider adding a comment to clarify this is test-specific behaviour:

+// Note: using a shared key across all component instances to test deduplication.
+// In production, use unique keys such as `async-data-${props.id}`.
 const { data } = await useAsyncData('async-data-component', async () => {
   await new Promise(resolve => setTimeout(resolve, 100))
   return `Hello from async data!`
 })
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ffa14fe and 047ab38.

📒 Files selected for processing (5)
  • packages/nuxt/src/app/composables/asyncData.ts (1 hunks)
  • test/basic.test.ts (1 hunks)
  • test/fixtures/basic/app/components/AsyncData.vue (1 hunks)
  • test/fixtures/basic/app/pages/async-data/multiple.vue (1 hunks)
  • test/nuxt/use-async-data.test.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T14:42:56.647Z
Learnt from: Tofandel
Repo: nuxt/nuxt PR: 33192
File: test/nuxt/use-async-data.test.ts:366-373
Timestamp: 2025-09-10T14:42:56.647Z
Learning: In the Nuxt useAsyncData test "should watch params deeply in a non synchronous way", the foo watcher intentionally updates both params.foo and params.locale using locale.value, simulating a scenario where one watcher consolidates multiple reactive values into a shared params object for testing debounced/non-synchronous behavior.

Applied to files:

  • test/fixtures/basic/app/pages/async-data/multiple.vue
  • test/nuxt/use-async-data.test.ts
  • test/fixtures/basic/app/components/AsyncData.vue
  • test/basic.test.ts
🧬 Code graph analysis (1)
test/nuxt/use-async-data.test.ts (1)
packages/nuxt/src/app/composables/asyncData.ts (1)
  • useAsyncData (194-431)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: code
🔇 Additional comments (2)
test/fixtures/basic/app/pages/async-data/multiple.vue (1)

1-9: LGTM! Simple test fixture for parallel async data behaviour.

The template correctly renders five AsyncData components with distinct IDs to test deduplication when multiple components use the same async data key.

test/basic.test.ts (1)

3150-3160: LGTM! E2E test confirms deduplication fix works correctly.

The test validates that all five component instances receive data ('Hello from async data!') rather than undefined when sharing the same async data key during SSR. This directly tests the fix for issue #33737.

@OrbisK OrbisK marked this pull request as draft November 20, 2025 18:49
@OrbisK
Copy link
Member Author

OrbisK commented Nov 20, 2025

I have to do some digging. This doesnt feel right.

@OrbisK OrbisK changed the title fix(nuxt): always return promise on async-data deduplication fix(nuxt): correctly handle initial promise on async-data deduplication Nov 20, 2025
@OrbisK OrbisK changed the title fix(nuxt): correctly handle initial promise on async-data deduplication fix(nuxt): async-data executes should return new promise on deduplication Nov 20, 2025
@OrbisK OrbisK marked this pull request as ready for review November 20, 2025 22:44

vi.useRealTimers()
})

Copy link
Member Author

@OrbisK OrbisK Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test does not detect the bug behaviour. Maybe we still want it 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pushed a test that seems to catch it!

if (typeof DOMException !== 'undefined' && error instanceof DOMException && error.name === 'AbortError') {
asyncData.status.value = 'idle'
return
return nuxtApp._asyncDataPromises[key]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we dont need this one

Copy link
Member

@danielroe danielroe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you ❤️

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
test/nuxt/use-async-data.test.ts (1)

1156-1195: New dedupe synchronisation test looks sound, with minor brittleness concern

The test correctly exercises the dedupe: 'cancel' path with multiple overlapping useAsyncData calls and verifies that all four returned promises resolve only after the final request completes and that they all observe the final value (data.value === 4). This matches the intent of ensuring that deduped executions share the latest resolving promise rather than earlier, now-cancelled ones.

One minor concern is that asserting promiseFn has been called exactly 4 times bakes in a specific internal calling pattern; if the implementation later changes (while preserving the public semantics that all deduped callers resolve together with the final value), this expectation could become brittle. If you do not intend to guarantee the exact number of handler invocations, you might relax this to assert the final data value and the shared resolution timing, and drop or soften the exact call-count assertion.

Otherwise, the timer usage and assertions look consistent with the rest of the suite.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 047ab38 and c763276.

📒 Files selected for processing (1)
  • test/nuxt/use-async-data.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Follow standard TypeScript conventions and best practices

Files:

  • test/nuxt/use-async-data.test.ts
**/*.{ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx,js,jsx,vue}: Use clear, descriptive variable and function names
Add comments only to explain complex logic or non-obvious implementations
Keep functions focused and manageable (generally under 50 lines), and extract complex logic into separate domain-specific files
Remove code that is not used or needed
Use error handling patterns consistently

Files:

  • test/nuxt/use-async-data.test.ts
**/*.{test,spec}.{ts,tsx,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Write unit tests for core functionality using vitest

Files:

  • test/nuxt/use-async-data.test.ts
🧠 Learnings (1)
📚 Learning: 2025-09-10T14:42:56.647Z
Learnt from: Tofandel
Repo: nuxt/nuxt PR: 33192
File: test/nuxt/use-async-data.test.ts:366-373
Timestamp: 2025-09-10T14:42:56.647Z
Learning: In the Nuxt useAsyncData test "should watch params deeply in a non synchronous way", the foo watcher intentionally updates both params.foo and params.locale using locale.value, simulating a scenario where one watcher consolidates multiple reactive values into a shared params object for testing debounced/non-synchronous behavior.

Applied to files:

  • test/nuxt/use-async-data.test.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (17)
  • GitHub Check: test-fixtures (windows-latest, built, rspack, async, manifest-on, json, lts/-1)
  • GitHub Check: test-fixtures (windows-latest, built, vite, async, manifest-off, json, lts/-1)
  • GitHub Check: test-fixtures (windows-latest, dev, vite-env-api, async, manifest-on, json, lts/-1)
  • GitHub Check: test-fixtures (ubuntu-latest, built, rspack, async, manifest-on, json, lts/-1)
  • GitHub Check: test-fixtures (ubuntu-latest, built, vite, async, manifest-on, json, lts/-1)
  • GitHub Check: test-fixtures (ubuntu-latest, built, vite, async, manifest-on, js, lts/-1)
  • GitHub Check: test-fixtures (ubuntu-latest, built, vite-env-api, async, manifest-on, json, lts/-1)
  • GitHub Check: test-fixtures (ubuntu-latest, dev, vite, default, manifest-on, json, lts/-1)
  • GitHub Check: test-fixtures (ubuntu-latest, dev, vite, async, manifest-on, js, lts/-1)
  • GitHub Check: test-fixtures (ubuntu-latest, dev, vite-env-api, default, manifest-on, json, lts/-1)
  • GitHub Check: test-fixtures (ubuntu-latest, dev, vite, async, manifest-off, json, lts/-1)
  • GitHub Check: release-pkg-pr-new
  • GitHub Check: typecheck (windows-latest, bundler)
  • GitHub Check: test-benchmark
  • GitHub Check: typecheck (ubuntu-latest, bundler)
  • GitHub Check: test-size
  • GitHub Check: code

@danielroe danielroe merged commit b31e4ee into nuxt:main Nov 25, 2025
56 checks passed
@github-actions github-actions bot mentioned this pull request Nov 25, 2025
@github-actions github-actions bot mentioned this pull request Dec 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

useAsyncData returns undefined in SSR when component is used multiple times

2 participants