From c268124ba096987bc00c728133093ecd6f056833 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 3 Jul 2025 15:34:03 -0400 Subject: [PATCH] add async_derived_orphan error --- .../98-reference/.generated/client-errors.md | 12 ++++++++++++ packages/svelte/messages/client-errors/errors.md | 10 ++++++++++ packages/svelte/src/internal/client/errors.js | 16 ++++++++++++++++ .../src/internal/client/reactivity/deriveds.js | 2 +- 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/documentation/docs/98-reference/.generated/client-errors.md b/documentation/docs/98-reference/.generated/client-errors.md index 3e4bbaded2..3f13edf338 100644 --- a/documentation/docs/98-reference/.generated/client-errors.md +++ b/documentation/docs/98-reference/.generated/client-errors.md @@ -1,5 +1,17 @@ +### async_derived_orphan + +``` +Cannot create a `$derived(...)` with an `await` expression outside of an effect tree +``` + +In Svelte there are two types of reaction — [`$derived`](https://svelte.dev/docs/svelte/$derived) and [`$effect`](https://svelte.dev/docs/svelte/$effect). Deriveds can be created anywhere, because they run _lazily_ and can be [garbage collected](https://developer.mozilla.org/en-US/docs/Glossary/Garbage_collection) if nothing references them. Effects, by contrast, keep running eagerly whenever their dependencies change, until they are destroyed. + +Because of this, effects can only be created inside other effects (or [effect roots](https://svelte.dev/docs/svelte/$effect#$effect.root), such as the one that is created when you first mount a component) so that Svelte knows when to destroy them. + +Some sleight of hand occurs when a derived contains an `await` expression: Since waiting until we read `{await getPromise()}` to call `getPromise` would be too late, we use an effect to instead call it proactively, notifying Svelte when the value is available. But since we're using an effect, we can only create asynchronous deriveds inside another effect. + ### bind_invalid_checkbox_value ``` diff --git a/packages/svelte/messages/client-errors/errors.md b/packages/svelte/messages/client-errors/errors.md index f2c5c1e865..9c7614f112 100644 --- a/packages/svelte/messages/client-errors/errors.md +++ b/packages/svelte/messages/client-errors/errors.md @@ -1,3 +1,13 @@ +## async_derived_orphan + +> Cannot create a `$derived(...)` with an `await` expression outside of an effect tree + +In Svelte there are two types of reaction — [`$derived`](https://svelte.dev/docs/svelte/$derived) and [`$effect`](https://svelte.dev/docs/svelte/$effect). Deriveds can be created anywhere, because they run _lazily_ and can be [garbage collected](https://developer.mozilla.org/en-US/docs/Glossary/Garbage_collection) if nothing references them. Effects, by contrast, keep running eagerly whenever their dependencies change, until they are destroyed. + +Because of this, effects can only be created inside other effects (or [effect roots](https://svelte.dev/docs/svelte/$effect#$effect.root), such as the one that is created when you first mount a component) so that Svelte knows when to destroy them. + +Some sleight of hand occurs when a derived contains an `await` expression: Since waiting until we read `{await getPromise()}` to call `getPromise` would be too late, we use an effect to instead call it proactively, notifying Svelte when the value is available. But since we're using an effect, we can only create asynchronous deriveds inside another effect. + ## bind_invalid_checkbox_value > Using `bind:value` together with a checkbox input is not allowed. Use `bind:checked` instead diff --git a/packages/svelte/src/internal/client/errors.js b/packages/svelte/src/internal/client/errors.js index 46ba3c7b14..3812eaed5b 100644 --- a/packages/svelte/src/internal/client/errors.js +++ b/packages/svelte/src/internal/client/errors.js @@ -389,4 +389,20 @@ export function state_unsafe_mutation() { } else { throw new Error(`https://svelte.dev/e/state_unsafe_mutation`); } +} + +/** + * Cannot create a `$derived(...)` with an `await` expression outside of an effect tree + * @returns {never} + */ +export function async_derived_orphan() { + if (DEV) { + const error = new Error(`async_derived_orphan\nCannot create a \`$derived(...)\` with an \`await\` expression outside of an effect tree\nhttps://svelte.dev/e/async_derived_orphan`); + + error.name = 'Svelte error'; + + throw error; + } else { + throw new Error(`https://svelte.dev/e/async_derived_orphan`); + } } \ No newline at end of file diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 45ca826838..05f34bebc9 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -100,7 +100,7 @@ export function async_derived(fn, location) { let parent = /** @type {Effect | null} */ (active_effect); if (parent === null) { - throw new Error('TODO cannot create unowned async derived'); + e.async_derived_orphan(); } var boundary = /** @type {Boundary} */ (parent.b);