diff --git a/documentation/docs/98-reference/.generated/client-errors.md b/documentation/docs/98-reference/.generated/client-errors.md index cd68ae704b..d894bbb270 100644 --- a/documentation/docs/98-reference/.generated/client-errors.md +++ b/documentation/docs/98-reference/.generated/client-errors.md @@ -74,6 +74,16 @@ Effect cannot be created inside a `$derived` value that was not itself created i Maximum update depth exceeded. This can happen when a reactive block or effect repeatedly sets a new value. Svelte limits the number of nested updates to prevent infinite loops ``` +### flush_sync_in_effect + +``` +Cannot use `flushSync` inside an effect +``` + +The `flushSync()` function can be used to flush any pending effects synchronously. It cannot be used if effects are currently being flushed — in other words, you can call it after a state change but _not_ inside an effect. + +This restriction only applies when using the `experimental.async` option, which will be active by default in Svelte 6. + ### hydration_failed ``` diff --git a/packages/svelte/messages/client-errors/errors.md b/packages/svelte/messages/client-errors/errors.md index f9e86dcd50..8a632abe3c 100644 --- a/packages/svelte/messages/client-errors/errors.md +++ b/packages/svelte/messages/client-errors/errors.md @@ -48,6 +48,14 @@ See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-long > Maximum update depth exceeded. This can happen when a reactive block or effect repeatedly sets a new value. Svelte limits the number of nested updates to prevent infinite loops +## flush_sync_in_effect + +> Cannot use `flushSync` inside an effect + +The `flushSync()` function can be used to flush any pending effects synchronously. It cannot be used if effects are currently being flushed — in other words, you can call it after a state change but _not_ inside an effect. + +This restriction only applies when using the `experimental.async` option, which will be active by default in Svelte 6. + ## hydration_failed > Failed to hydrate the application diff --git a/packages/svelte/src/internal/client/errors.js b/packages/svelte/src/internal/client/errors.js index 063595884e..07e3e1974c 100644 --- a/packages/svelte/src/internal/client/errors.js +++ b/packages/svelte/src/internal/client/errors.js @@ -334,4 +334,19 @@ export function state_unsafe_mutation() { } else { throw new Error(`https://svelte.dev/e/state_unsafe_mutation`); } +} + +/** + * Cannot use `flushSync` inside an effect + * @returns {never} + */ +export function flush_sync_in_effect() { + if (DEV) { + const error = new Error(`flush_sync_in_effect\nCannot use \`flushSync\` inside an effect\nhttps://svelte.dev/e/flush_sync_in_effect`); + + error.name = 'Svelte error'; + throw error; + } else { + throw new Error(`https://svelte.dev/e/flush_sync_in_effect`); + } } \ No newline at end of file diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 846a4e7648..ae3cc36c34 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -706,6 +706,10 @@ export function process_effects(batch, root) { * @returns {T} */ export function flushSync(fn) { + if (async_mode_flag && active_effect !== null) { + e.flush_sync_in_effect(); + } + var result; const batch = Batch.ensure(); diff --git a/packages/svelte/src/legacy/legacy-client.js b/packages/svelte/src/legacy/legacy-client.js index 45c478ecab..4ff1e619d5 100644 --- a/packages/svelte/src/legacy/legacy-client.js +++ b/packages/svelte/src/legacy/legacy-client.js @@ -10,6 +10,7 @@ import * as w from '../internal/client/warnings.js'; import { DEV } from 'esm-env'; import { FILENAME } from '../constants.js'; import { component_context, dev_current_component_function } from '../internal/client/context.js'; +import { async_mode_flag } from '../internal/flags/index.js'; /** * Takes the same options as a Svelte 4 component and the component function and returns a Svelte 4 compatible component. @@ -119,8 +120,9 @@ class Svelte4Component { recover: options.recover }); - // We don't flushSync for custom element wrappers or if the user doesn't want it - if (!options?.props?.$$host || options.sync === false) { + // We don't flushSync for custom element wrappers or if the user doesn't want it, + // or if we're in async mode since `flushSync()` will fail + if (!async_mode_flag && (!options?.props?.$$host || options.sync === false)) { flushSync(); } diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index 0e952db7ec..e21755705f 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -6,7 +6,8 @@ import { effect, effect_root, render_effect, - user_effect + user_effect, + user_pre_effect } from '../../src/internal/client/reactivity/effects'; import { state, set, update, update_pre } from '../../src/internal/client/reactivity/sources'; import type { Derived, Effect, Source, Value } from '../../src/internal/client/types'; @@ -1079,17 +1080,17 @@ describe('signals', () => { test('nested effects depend on state of upper effects', () => { const logs: number[] = []; - user_effect(() => { + user_pre_effect(() => { const raw = state(0); const proxied = proxy({ current: 0 }); // We need those separate, else one working and rerunning the effect // could mask the other one not rerunning - user_effect(() => { + user_pre_effect(() => { logs.push($.get(raw)); }); - user_effect(() => { + user_pre_effect(() => { logs.push(proxied.current); }); @@ -1097,7 +1098,7 @@ describe('signals', () => { // together with the reading effects flushSync(); - user_effect(() => { + user_pre_effect(() => { $.untrack(() => { set(raw, $.get(raw) + 1); proxied.current += 1;