Skip to content

Implement immediate/synchronous effects #2822

@gbj

Description

@gbj

Is your feature request related to a problem? Please describe.

0.7 changes effect scheduling to automatically batch effects, and to run them asynchronously, as determined by whichever async runtime is being used. In the default web case, this is wasm-bindgen-futures, which uses queueMicrotask. In other words, updating a signal updates its value immediately, but runs effects on the next microtask. This means that an effect does not run immediately, once per signal change.

In general, this is good, because effects are intended to synchronize the current/latest value of a signal with the outside world in some way that is typically orders of magnitude more expensive than actually updating the signal: for example, updating a DOM node.

This has several benefits:

  1. Updating a signal multiple times, synchronously, does not run that expensive effect (like updating a DOM node) multiple times in a way that's unnecessary
  2. Updating multiple signals read in an effect does not run that effect multiple times. (Currently, users can opt into batching using batch(); the number of bug reports in which the solution was "use batch()" and that wasn't obvious to the user is more than a handful.)

The canonical example of why this is good would be something like

let a = RwSignal::new(1);
let b = RwSignal::new(2);

Effect::new(move |_| {
  let value = a.get() + b.get();
  // do something expensive with `value`, like updating the DOM
});

// ... 
on:click=move |_| {
  a.set(3);
  b.set(4);
}

// how many times did you want the effect to run?
// did you really intend there to be an inconsistent state
// where the effect had the old value of `b` and new value of `a`?

However, there are some situations in which users want an effect that runs immediately when signals are changed, and that does not batch in this way.

Describe the solution you'd like

An alternative effect type whose ReactiveGraph and Subscriber implementations are designed such that, when marked check or dirty, it simply runs its inner function, instead of triggering a notification to wake up an async task.

This means Effect and render effects (created for closures passed into the view, for example) run in the async, batched way, but users can opt into immediate effects for other cases.

Describe alternatives you've considered

  1. Making all effects immediate, and requiring opt-in batching. (Status quo ante 0.7)
  2. A flush_sync() that allows you to tell all effects to run now, not on the next microtask. (Difficult to implement because the async runtimes we piggyback on don't expose what we need to do this.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions