disallow `flushSync()` inside effects

pull/15844/head
Rich Harris 3 months ago
parent e912f885bd
commit 0430c76da3

@ -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
```

@ -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

@ -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`);
}
}

@ -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();

@ -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();
}

@ -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;

Loading…
Cancel
Save