Skip to content

Commit 1ca795b

Browse files
committed
Merge branch 'main' into async-fixes
2 parents 26f46ba + 6837246 commit 1ca795b

File tree

2 files changed

+55
-52
lines changed
  • packages/svelte
    • src/internal/client/reactivity
    • tests/runtime-runes/samples/effect-order-7

2 files changed

+55
-52
lines changed

packages/svelte/src/internal/client/reactivity/batch.js

Lines changed: 47 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -195,15 +195,15 @@ export class Batch {
195195
// if we didn't start any new async work, and no async work
196196
// is outstanding from a previous flush, commit
197197
if (this.#async_effects.length === 0 && this.#pending === 0) {
198+
this.#commit();
199+
198200
var render_effects = this.#render_effects;
199201
var effects = this.#effects;
200202

201203
this.#render_effects = [];
202204
this.#effects = [];
203205
this.#block_effects = [];
204206

205-
this.#commit();
206-
207207
flush_queued_effects(render_effects);
208208
flush_queued_effects(effects);
209209

@@ -544,60 +544,59 @@ function flush_queued_effects(effects) {
544544
var length = effects.length;
545545
if (length === 0) return;
546546

547-
for (var i = 0; i < length; i++) {
548-
var effect = effects[i];
549-
550-
if ((effect.f & (DESTROYED | INERT)) === 0) {
551-
if (is_dirty(effect)) {
552-
var wv = write_version;
553-
var current_size = /** @type {Batch} */ (current_batch).current.size;
554-
555-
update_effect(effect);
556-
557-
// Effects with no dependencies or teardown do not get added to the effect tree.
558-
// Deferred effects (e.g. `$effect(...)`) _are_ added to the tree because we
559-
// don't know if we need to keep them until they are executed. Doing the check
560-
// here (rather than in `update_effect`) allows us to skip the work for
561-
// immediate effects.
562-
if (effect.deps === null && effect.first === null && effect.nodes_start === null) {
563-
// if there's no teardown or abort controller we completely unlink
564-
// the effect from the graph
565-
if (effect.teardown === null && effect.ac === null) {
566-
// remove this effect from the graph
567-
unlink_effect(effect);
568-
} else {
569-
// keep the effect in the graph, but free up some memory
570-
effect.fn = null;
571-
}
547+
var i = 0;
548+
549+
while (i < length) {
550+
var effect = effects[i++];
551+
552+
if ((effect.f & (DESTROYED | INERT)) === 0 && is_dirty(effect)) {
553+
var wv = write_version;
554+
var current_size = /** @type {Batch} */ (current_batch).current.size;
555+
556+
update_effect(effect);
557+
558+
// Effects with no dependencies or teardown do not get added to the effect tree.
559+
// Deferred effects (e.g. `$effect(...)`) _are_ added to the tree because we
560+
// don't know if we need to keep them until they are executed. Doing the check
561+
// here (rather than in `update_effect`) allows us to skip the work for
562+
// immediate effects.
563+
if (effect.deps === null && effect.first === null && effect.nodes_start === null) {
564+
// if there's no teardown or abort controller we completely unlink
565+
// the effect from the graph
566+
if (effect.teardown === null && effect.ac === null) {
567+
// remove this effect from the graph
568+
unlink_effect(effect);
569+
} else {
570+
// keep the effect in the graph, but free up some memory
571+
effect.fn = null;
572572
}
573+
}
573574

574-
// if state is written in a user effect, abort and re-schedule, lest we run
575-
// effects that should be removed as a result of the state change
576-
if (write_version > wv && (effect.f & USER_EFFECT) !== 0) {
577-
// Work needs to happen in a separate batch, else prior sources would be mixed with
578-
// newly updated sources, which could lead to infinite loops when effects run over and over again.
579-
// We need to bring over the just written sources though to correctly mark the right reactions as dirty.
580-
var old_batch = /** @type {Batch} */ (current_batch);
581-
batches.delete(old_batch);
582-
current_batch = null;
583-
var new_batch = Batch.ensure(!flushing_sync);
584-
var current_idx = 0;
585-
// We're taking advantage of the spec here which says that entries in a Map are traversed by insertion order
586-
for (const source of old_batch.current) {
587-
if (current_idx >= current_size) {
588-
new_batch.capture(source[0], source[1]);
589-
}
590-
current_idx++;
575+
// if state is written in a user effect, abort and re-schedule, lest we run
576+
// effects that should be removed as a result of the state change
577+
if (write_version > wv && (effect.f & USER_EFFECT) !== 0) {
578+
// Work needs to happen in a separate batch, else prior sources would be mixed with
579+
// newly updated sources, which could lead to infinite loops when effects run over and over again.
580+
// We need to bring over the just written sources though to correctly mark the right reactions as dirty.
581+
var old_batch = /** @type {Batch} */ (current_batch);
582+
batches.delete(old_batch);
583+
current_batch = null;
584+
var new_batch = Batch.ensure(!flushing_sync);
585+
var current_idx = 0;
586+
// We're taking advantage of the spec here which says that entries in a Map are traversed by insertion order
587+
for (const source of old_batch.current) {
588+
if (current_idx >= current_size) {
589+
new_batch.capture(source[0], source[1]);
591590
}
592-
i++;
593-
break;
591+
current_idx++;
594592
}
593+
break;
595594
}
596595
}
597596
}
598597

599-
for (; i < length; i += 1) {
600-
schedule_effect(effects[i]);
598+
while (i < length) {
599+
schedule_effect(effects[i++]);
601600
}
602601
}
603602

packages/svelte/tests/runtime-runes/samples/effect-order-7/_config.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@ import { flushSync } from 'svelte';
22
import { test } from '../../test';
33

44
export default test({
5-
skip: true,
5+
// For this to work in non-async mode, we would need to abort
6+
// inside `#traverse_effect_tree`, which would be very
7+
// complicated and annoying. Since this hasn't been
8+
// a real issue (AFAICT), we ignore it
9+
skip_no_async: true,
610

7-
async test({ assert, target, logs }) {
11+
async test({ target }) {
812
const [open, close] = target.querySelectorAll('button');
913

1014
flushSync(() => open.click());
11-
flushSync(() => close.click());
1215

13-
assert.deepEqual(logs, [true]);
16+
// if the effect queue isn't aborted after the state change, this will throw
17+
flushSync(() => close.click());
1418
}
1519
});

0 commit comments

Comments
 (0)