@@ -348,15 +348,13 @@ export class Batch {
348348 var previous_batch_values = batch_values ;
349349 var is_earlier = true ;
350350
351- /** @type {EffectTarget } */
352- var dummy_target = {
353- parent : null ,
354- effect : null ,
355- effects : [ ] ,
356- render_effects : [ ] ,
357- block_effects : [ ]
358- } ;
351+ /** @type {Map<Batch, Set<Effect>> } */
352+ const batch_effects = new Map ( ) ;
359353
354+ // First loop: collect effects to run for each batch
355+ // Do this before running them because rerunning an effect
356+ // might change its dependencies and so other batches could
357+ // run effects when they shouldn't or not when they should.
360358 for ( const batch of batches ) {
361359 if ( batch === this ) {
362360 is_earlier = false ;
@@ -388,27 +386,53 @@ export class Batch {
388386 // Re-run async/block effects that depend on distinct values changed in both batches
389387 const others = [ ...batch . current . keys ( ) ] . filter ( ( s ) => ! this . current . has ( s ) ) ;
390388 if ( others . length > 0 ) {
389+ /** @type {Set<Effect> } */
390+ const effects = new Set ( ) ;
391391 /** @type {Set<Value> } */
392392 const marked = new Set ( ) ;
393393 /** @type {Map<Reaction, boolean> } */
394394 const checked = new Map ( ) ;
395395 for ( const source of sources ) {
396- mark_effects ( source , others , marked , checked ) ;
396+ mark_effects ( source , others , effects , marked , checked ) ;
397397 }
398398
399- if ( queued_root_effects . length > 0 ) {
400- current_batch = batch ;
401- batch . apply ( ) ;
399+ if ( effects . size > 0 ) {
400+ batch_effects . set ( batch , effects ) ;
401+ }
402+ }
403+ }
402404
405+ // Second loop: schedule effects and traverse effect trees
406+ if ( batch_effects . size > 0 ) {
407+ /** @type {EffectTarget } */
408+ var dummy_target = {
409+ parent : null ,
410+ effect : null ,
411+ effects : [ ] ,
412+ render_effects : [ ] ,
413+ block_effects : [ ]
414+ } ;
415+
416+ for ( const [ batch , effects ] of batch_effects ) {
417+ current_batch = batch ;
418+ batch . apply ( ) ;
419+
420+ for ( const effect of effects ) {
421+ set_signal_status ( effect , DIRTY ) ;
422+ schedule_effect ( effect ) ;
423+ }
424+
425+ if ( queued_root_effects . length > 0 ) {
403426 for ( const root of queued_root_effects ) {
404427 batch . #traverse_effect_tree( root , dummy_target ) ;
405428 }
406429
407430 // TODO do we need to do anything with `target`? defer block effects?
408431
409432 queued_root_effects = [ ] ;
410- batch . deactivate ( ) ;
411433 }
434+
435+ batch . deactivate ( ) ;
412436 }
413437 }
414438
@@ -710,10 +734,11 @@ function flush_queued_effects(effects) {
710734 * these effects can re-run after another batch has been committed
711735 * @param {Value } value
712736 * @param {Source[] } sources
737+ * @param {Set<Effect> } effects
713738 * @param {Set<Value> } marked
714739 * @param {Map<Reaction, boolean> } checked
715740 */
716- function mark_effects ( value , sources , marked , checked ) {
741+ function mark_effects ( value , sources , effects , marked , checked ) {
717742 if ( marked . has ( value ) ) return ;
718743 marked . add ( value ) ;
719744
@@ -722,14 +747,13 @@ function mark_effects(value, sources, marked, checked) {
722747 const flags = reaction . f ;
723748
724749 if ( ( flags & DERIVED ) !== 0 ) {
725- mark_effects ( /** @type {Derived } */ ( reaction ) , sources , marked , checked ) ;
750+ mark_effects ( /** @type {Derived } */ ( reaction ) , sources , effects , marked , checked ) ;
726751 } else if (
727752 ( flags & ( ASYNC | BLOCK_EFFECT ) ) !== 0 &&
728- ( flags & DIRTY ) === 0 && // we may have scheduled this one already
753+ ! effects . has ( /** @type { Effect } */ ( reaction ) ) &&
729754 depends_on ( reaction , sources , checked )
730755 ) {
731- set_signal_status ( reaction , DIRTY ) ;
732- schedule_effect ( /** @type {Effect } */ ( reaction ) ) ;
756+ effects . add ( /** @type {Effect } */ ( reaction ) ) ;
733757 }
734758 }
735759 }
0 commit comments