Skip to content

Commit 0430c76

Browse files
committed
disallow flushSync() inside effects
1 parent e912f88 commit 0430c76

File tree

6 files changed

+47
-7
lines changed

6 files changed

+47
-7
lines changed

documentation/docs/98-reference/.generated/client-errors.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ Effect cannot be created inside a `$derived` value that was not itself created i
7474
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
7575
```
7676

77+
### flush_sync_in_effect
78+
79+
```
80+
Cannot use `flushSync` inside an effect
81+
```
82+
83+
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.
84+
85+
This restriction only applies when using the `experimental.async` option, which will be active by default in Svelte 6.
86+
7787
### hydration_failed
7888

7989
```

packages/svelte/messages/client-errors/errors.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-long
4848

4949
> 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
5050
51+
## flush_sync_in_effect
52+
53+
> Cannot use `flushSync` inside an effect
54+
55+
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.
56+
57+
This restriction only applies when using the `experimental.async` option, which will be active by default in Svelte 6.
58+
5159
## hydration_failed
5260

5361
> Failed to hydrate the application

packages/svelte/src/internal/client/errors.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,4 +334,19 @@ export function state_unsafe_mutation() {
334334
} else {
335335
throw new Error(`https://svelte.dev/e/state_unsafe_mutation`);
336336
}
337+
}
338+
339+
/**
340+
* Cannot use `flushSync` inside an effect
341+
* @returns {never}
342+
*/
343+
export function flush_sync_in_effect() {
344+
if (DEV) {
345+
const error = new Error(`flush_sync_in_effect\nCannot use \`flushSync\` inside an effect\nhttps://svelte.dev/e/flush_sync_in_effect`);
346+
347+
error.name = 'Svelte error';
348+
throw error;
349+
} else {
350+
throw new Error(`https://svelte.dev/e/flush_sync_in_effect`);
351+
}
337352
}

packages/svelte/src/internal/client/runtime.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,10 @@ export function process_effects(batch, root) {
706706
* @returns {T}
707707
*/
708708
export function flushSync(fn) {
709+
if (async_mode_flag && active_effect !== null) {
710+
e.flush_sync_in_effect();
711+
}
712+
709713
var result;
710714

711715
const batch = Batch.ensure();

packages/svelte/src/legacy/legacy-client.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import * as w from '../internal/client/warnings.js';
1010
import { DEV } from 'esm-env';
1111
import { FILENAME } from '../constants.js';
1212
import { component_context, dev_current_component_function } from '../internal/client/context.js';
13+
import { async_mode_flag } from '../internal/flags/index.js';
1314

1415
/**
1516
* 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 {
119120
recover: options.recover
120121
});
121122

122-
// We don't flushSync for custom element wrappers or if the user doesn't want it
123-
if (!options?.props?.$$host || options.sync === false) {
123+
// We don't flushSync for custom element wrappers or if the user doesn't want it,
124+
// or if we're in async mode since `flushSync()` will fail
125+
if (!async_mode_flag && (!options?.props?.$$host || options.sync === false)) {
124126
flushSync();
125127
}
126128

packages/svelte/tests/signals/test.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
effect,
77
effect_root,
88
render_effect,
9-
user_effect
9+
user_effect,
10+
user_pre_effect
1011
} from '../../src/internal/client/reactivity/effects';
1112
import { state, set, update, update_pre } from '../../src/internal/client/reactivity/sources';
1213
import type { Derived, Effect, Source, Value } from '../../src/internal/client/types';
@@ -1079,25 +1080,25 @@ describe('signals', () => {
10791080
test('nested effects depend on state of upper effects', () => {
10801081
const logs: number[] = [];
10811082

1082-
user_effect(() => {
1083+
user_pre_effect(() => {
10831084
const raw = state(0);
10841085
const proxied = proxy({ current: 0 });
10851086

10861087
// We need those separate, else one working and rerunning the effect
10871088
// could mask the other one not rerunning
1088-
user_effect(() => {
1089+
user_pre_effect(() => {
10891090
logs.push($.get(raw));
10901091
});
10911092

1092-
user_effect(() => {
1093+
user_pre_effect(() => {
10931094
logs.push(proxied.current);
10941095
});
10951096

10961097
// Important so that the updating effect is not running
10971098
// together with the reading effects
10981099
flushSync();
10991100

1100-
user_effect(() => {
1101+
user_pre_effect(() => {
11011102
$.untrack(() => {
11021103
set(raw, $.get(raw) + 1);
11031104
proxied.current += 1;

0 commit comments

Comments
 (0)