Skip to content

fix(core): clear cached promise in lazy() on rejection (#2999) - #3002

Closed
webdevsamran wants to merge 1 commit into
solidjs:mainfrom
webdevsamran:fix/lazy-clear-rejection
Closed

fix(core): clear cached promise in lazy() on rejection (#2999)#3002
webdevsamran wants to merge 1 commit into
solidjs:mainfrom
webdevsamran:fix/lazy-clear-rejection

Conversation

@webdevsamran

Copy link
Copy Markdown

Summary

Clears the cached promise in lazy() on rejection, allowing dynamic imports that fail due to transient network or chunk-load errors to be retried on subsequent renders/preloads without requiring a hard page reload or permanently poisoning SSR.

Problem

In both client (packages/solid/src/render/component.ts) and server (packages/solid/src/server/rendering.ts) implementations of lazy(), the promise returned by fn() was cached permanently in closure variable p.

If fn() rejected:

  1. Client: An Error Boundary retry flow (e.g. <Errored fallback={(_, reset) => <button onClick={reset}>Retry</button>}>) could never recover. Resetting/remounting the boundary simply re-observed the cached rejected promise without invoking fn() again.
  2. Server: Module-scoped lazy promises that rejected would poison all subsequent SSR requests for the process lifetime.
  3. Unhandled Rejections: The internal .then() calls lacked a rejection handler, emitting unhandled promise rejections.

Solution

  1. In both client and server lazy(), attach a rejection callback to the active promise cur that clears p (if (p === cur) p = undefined).
  2. Subsequent load() / preload() invocations will call fn() afresh.
  3. If fn() succeeds on retry, comp / resolved is populated normally.
  4. Added regression unit test in packages/solid/web/test/lazy.spec.tsx verifying that lazy().preload() retries fn() after an initial rejection.

Fixes #2999

When lazy(fn) runs, if the dynamic import promise rejected (e.g. transient network
or chunk load failure), the rejected promise was cached indefinitely.

This caused:
1. Client: ErrorBoundary / Errored retry flows could never recover because remounting
   the component returned the cached rejected promise without invoking fn() again.
2. Server: Module-scoped lazy promises that rejected would poison all subsequent SSR
   requests for the lifetime of the process.
3. Unhandled rejection when fn() rejects since previous .then() had no rejection handler.

This patch:
- In both client and server lazy(), clears the cached promise reference p if the
  promise rejects, allowing subsequent load() / preload() calls to retry fn().
- Added unit test in lazy.spec.tsx verifying retry behavior after rejection.

Fixes solidjs#2999
@changeset-bot

changeset-bot Bot commented Aug 16, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 9b55451

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@ryansolid

Copy link
Copy Markdown
Member

Landed on main in 7700341, combined with #2955. The rejected promise is no longer cached forever — preload() and the next SSR request call fn() again. Thanks.

@ryansolid ryansolid closed this Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[2.0] lazy() caches rejected module promises: retry is impossible on the client and one failed import poisons SSR for the process lifetime

2 participants