Skip to content

fix(runtime-core): skip async component callbacks after unmount#14911

Open
baozjj wants to merge 2 commits into
vuejs:mainfrom
baozjj:fix/async-component-callback-after-unmount
Open

fix(runtime-core): skip async component callbacks after unmount#14911
baozjj wants to merge 2 commits into
vuejs:mainfrom
baozjj:fix/async-component-callback-after-unmount

Conversation

@baozjj
Copy link
Copy Markdown
Contributor

@baozjj baozjj commented Jun 3, 2026

What is the motivation / use case?

Example: A route lazy-loads a heavy chart with defineAsyncComponent({ timeout: 10000 }). The user opens the page, sees the loading state, then navigates away after 2 seconds. About 8 seconds later, the app is already on another page, but the console / global errorHandler (e.g. Sentry) still reports Async component timed out after 10000ms. — even though that async component has already been unmounted.

In the non-Suspense setup path, delay / timeout timers and load().then() / load().catch() do not check instance.isUnmounted, and timers are not cleared on unmount. That can trigger onError()handleError() after teardown. The __asyncHydrate path already uses !instance.isUnmounted; this change aligns the setup path with that behavior.

What is the solution?

  • Register onUnmounted to clearTimeout for delay and timeout timers
  • In delay, timeout, and load() resolve/reject handlers, return early when instance.isUnmounted

What is the scope of this change?

packages/runtime-core/src/apiAsyncComponent.ts and regression tests in apiAsyncComponent.spec.ts only.

Test plan

  • pnpm test packages/runtime-core/__tests__/apiAsyncComponent.spec.ts --run (24 passed)
  • should not call errorHandler after unmount (timeout) — unmount before timeout fires
  • should not call errorHandler after unmount (loader error) — unmount before rejected loader is handled

Summary by CodeRabbit

  • Bug Fixes

    • Prevented async components from triggering app error handlers after they've been unmounted (including when pending, timing out, or when a previous loader rejects).
  • Tests

    • Added tests covering unmount-before-timeout, unmount-before-loader-rejection, and remount-after-ignored-rejection scenarios to ensure error handlers aren't called spuriously.

Clear delay/timeout timers on unmount and guard load/timeout handlers
with isUnmounted to avoid spurious errorHandler invocations.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 3, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ed0c020f-bacc-48fe-997f-a8183e7ca0d9

📥 Commits

Reviewing files that changed from the base of the PR and between 767d392 and d06aba4.

📒 Files selected for processing (2)
  • packages/runtime-core/__tests__/apiAsyncComponent.spec.ts
  • packages/runtime-core/src/apiAsyncComponent.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/runtime-core/src/apiAsyncComponent.ts

📝 Walkthrough

Walkthrough

defineAsyncComponent now clears delay/timeout timers on unmount and returns early from loader resolution/rejection if the instance is unmounted; tests added to ensure app.config.errorHandler is not called when async components unmount before timeout or loader rejection, and to verify remount behavior.

Changes

Async component unmount lifecycle guards

Layer / File(s) Summary
Timer cleanup and loader state guards
packages/runtime-core/src/apiAsyncComponent.ts
Imports onUnmounted, introduces delayTimer and timeoutTimer cleared on unmount, and adds instance.isUnmounted early returns in loader success and error paths to prevent state updates and onError calls after unmount.
Unmount lifecycle test coverage
packages/runtime-core/__tests__/apiAsyncComponent.spec.ts
Adds three tests: timeout-unmount (unmount before timeout fires), loader-reject-unmount (unmount before loader rejects), and remount-after-unmount (ignore rejected loader, remount triggers loader again and resolves), asserting app.config.errorHandler is not invoked.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

:hammer: p3-minor-bug, ready for review

Suggested reviewers

  • edison1105

Poem

🐰 Timers cleared with gentle paws,
Unmounted leaves avoid the cause,
No phantom errors wake the night,
Loaders hush and keep things right,
I hop away, the app sleeps tight.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(runtime-core): skip async component callbacks after unmount' directly and concisely summarizes the main change: preventing async component callbacks from executing after the component unmounts.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jun 3, 2026

Open in StackBlitz

@vue/compiler-core

pnpm add https://pkg.pr.new/@vue/compiler-core@14911
npm i https://pkg.pr.new/@vue/compiler-core@14911
yarn add https://pkg.pr.new/@vue/compiler-core@14911.tgz

@vue/compiler-dom

pnpm add https://pkg.pr.new/@vue/compiler-dom@14911
npm i https://pkg.pr.new/@vue/compiler-dom@14911
yarn add https://pkg.pr.new/@vue/compiler-dom@14911.tgz

@vue/compiler-sfc

pnpm add https://pkg.pr.new/@vue/compiler-sfc@14911
npm i https://pkg.pr.new/@vue/compiler-sfc@14911
yarn add https://pkg.pr.new/@vue/compiler-sfc@14911.tgz

@vue/compiler-ssr

pnpm add https://pkg.pr.new/@vue/compiler-ssr@14911
npm i https://pkg.pr.new/@vue/compiler-ssr@14911
yarn add https://pkg.pr.new/@vue/compiler-ssr@14911.tgz

@vue/reactivity

pnpm add https://pkg.pr.new/@vue/reactivity@14911
npm i https://pkg.pr.new/@vue/reactivity@14911
yarn add https://pkg.pr.new/@vue/reactivity@14911.tgz

@vue/runtime-core

pnpm add https://pkg.pr.new/@vue/runtime-core@14911
npm i https://pkg.pr.new/@vue/runtime-core@14911
yarn add https://pkg.pr.new/@vue/runtime-core@14911.tgz

@vue/runtime-dom

pnpm add https://pkg.pr.new/@vue/runtime-dom@14911
npm i https://pkg.pr.new/@vue/runtime-dom@14911
yarn add https://pkg.pr.new/@vue/runtime-dom@14911.tgz

@vue/server-renderer

pnpm add https://pkg.pr.new/@vue/server-renderer@14911
npm i https://pkg.pr.new/@vue/server-renderer@14911
yarn add https://pkg.pr.new/@vue/server-renderer@14911.tgz

@vue/shared

pnpm add https://pkg.pr.new/@vue/shared@14911
npm i https://pkg.pr.new/@vue/shared@14911
yarn add https://pkg.pr.new/@vue/shared@14911.tgz

vue

pnpm add https://pkg.pr.new/vue@14911
npm i https://pkg.pr.new/vue@14911
yarn add https://pkg.pr.new/vue@14911.tgz

@vue/compat

pnpm add https://pkg.pr.new/@vue/compat@14911
npm i https://pkg.pr.new/@vue/compat@14911
yarn add https://pkg.pr.new/@vue/compat@14911.tgz

commit: d06aba4

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 106 kB (+155 B) 40.1 kB (+61 B) 36.1 kB (+46 B)
vue.global.prod.js 164 kB (+155 B) 60.1 kB (+58 B) 53.5 kB (-1 B)

Usages

Name Size Gzip Brotli
createApp (CAPI only) 48.8 kB 19 kB 17.4 kB
createApp 56.9 kB 22 kB 20.1 kB
createSSRApp 61.2 kB 23.8 kB 21.7 kB
defineCustomElement 63.1 kB 23.9 kB 21.8 kB
overall 71.7 kB 27.4 kB 25 kB

@edison1105 edison1105 added ready to merge The PR is ready to be merged. 🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. labels Jun 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. ready to merge The PR is ready to be merged.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants