[2.0] Proposal: `independent()` for separate atomic update groups
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Aptitud para principiantes
- 35/100
- Tipo de issue
- Nueva funcionalidad
- Claridad
- Bastante claro
- Estado de actividad
- Activo
- Stack tecnológico
- javascript, typescript
- Área
- frontend
Línea de trabajo
Read the proposed semantics and the prototype at https://github.com/Monkeylordz/solid/pull/1; the issue does not name repository files or tests. This is a request for feedback on whether the primitive belongs in core and on its semantics and name, so a concrete implementation task would need to be agreed before work can be considered done.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
I’d like to propose an independent primitive that lets synchronous writes form an atomic update group that can commit independently of unrelated work in the surrounding batch.
Motivation
The motivating case is a component updating its own state and then invoking a consumer callback:
function selectItem(item) {
setMenuOpen(false);
props.onSelect(item); // may trigger async data loading
}
If the callback starts async work, the menu close can be held along with it, even though those updates are unrelated.
As the component author, I want to preserve normal batching while keeping the component’s local update separate from work started by its consumer:
function selectItem(item) {
independent(() => setMenuOpen(false));
props.onSelect(item);
}
The menu can then close at the normal microtask flush while the selection waits for data. Reversing those two calls preserves the separation when their reactive graphs do not overlap.
Details
The proposed semantics are:
- The callback runs synchronously; its writes still use normal batching and effect phases.
- Writes within a group commit together. Async work triggered and observed through that group can still hold the whole group.
- Separate or nested calls create separate groups.
- Independence is conditional: overlapping writes, shared derivations, or writes to already held state can merge groups. Sharing a render effect alone does not merge them.
- The scope ends when the callback returns. Writes after an
awaitare outside it, and throws do not roll back writes already made.
I have a complete implementation in my development fork for review, you are free to use it in its entirety:
https://github.com/Monkeylordz/solid/pull/1
It almost completely treeshakes away when independent is not imported, and importing it adds approximately 1.04 kB Brotli.
This is also something I think can only be done inside the core signals runtime - a third-party library cannot control which writes share a transaction or how those transactions merge and commit through the existing public APIs.
Alternatives & why they don't work:
I went through my own (private) component library and found roughly 50 places where a component writes state and then calls a props.onX callback. Handling these required different combinations of flush, latest, and setTimeout, depending on the surrounding code.
flush:
Using flush between the component’s write and the consumer callback can separate their updates. However, it drains globally pending work, including ready updates made by the caller:
function select() {
setA(...);
flush();
setB(...);
}
setConsumerState(...);
select(); // also flushes the caller's ready pending work
setOtherConsumerState(...);
The consumer now needs to know that select() contains a flush and account for that when ordering its own writes. Adding or moving an internal flush can change observable behavior outside the component, even though its public API has not changed. However, reordering calls is not always practical: functions take parameters, return values, and perform other side effects that constrain their order. In my library, I ended up making some callbacks return () => void “report” functions. Components had to manage those functions and call them at the end of their handlers so that internal flushes would not interfere with consumer callbacks. Furthermore, this requires coordination across every layer - consumers can add their own flushes and disrupt intended behavior. It is difficult to maintain as a general library pattern.
flush also changes when effects run and can make them run more times than intended.
latest:
latest moves responsibility from the operation that needs independence to the code consuming its state. A component library may expose that state through accessors, controlled props, context, or callbacks. Each relevant reader then needs to know when to use latest, including readers supplied by consumers. Adding a new ordinary read can reintroduce the original behavior.
It also changes which values readers observe without separating the underlying writes into their own commit group. If an operation updates several signals, selectively reading some through latest does not express that those writes should commit together independently of unrelated work.
Finally, latest still follows its own async consistency rules: rendered async work derived from its view can hold that view. It is useful for deliberately displaying in-flight state, but making a component’s update independent through reader annotations requires coordination across the component and its consumers.
setTimeout:
Moving a write into a later task can separate it from the current batch, but introduces several other concerns:
- Discoverability and scheduling assumptions - Authors would need to know which scheduling boundary produces the intended behavior. For example,
queueMicrotaskis sensitive to whether Solid’s flush has already been queued, so replacing one scheduling mechanism with another is not straightforward. - Visible intermediate states - Deferring to another task permits browser paint and other callbacks to run between the original handler and the deferred update.
- Cleanup and cancellation - Components must cancel obsolete writes when they are disposed or when a newer interaction supersedes the scheduled one. That adds manual timer bookkeeping or generation checks to otherwise synchronous handlers.
- Write ordering and stale state - A deferred write runs after writes made inline in the meantime and can overwrite a newer value. Capturing values before scheduling risks stale inputs; reading them later changes which state the operation observes.
- Lost execution context and intent - A plain timer callback does not automatically retain the originating action’s transaction context. It also gives Solid no explicit indication that the scheduling boundary was intended to separate update groups, making that intent harder to represent in diagnostics.
independent would let the component express that boundary at the write site while preserving synchronous execution and normal batching, reducing the coordination required from callers.
Would this be a useful primitive to support in core? I’d particularly appreciate feedback on the semantics, the name, and interactions that the prototype should cover. Again, a complete implementation is available to see here: https://github.com/Monkeylordz/solid/pull/1.
- Lenguaje dominante
- TypeScript
- Estrellas
- 36.2k
- Forks
- 1.1k
- Merge medio
- 11 h 48 min
- PR fusionados (30 d)
- 441
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de solidjs/solid
-
Dificultad 3/5 1-2 días Aptitud para principiantes 52/100
Los mantenedores suelen responder en 1 día
-
Dificultad 3/5 1-2 días Aptitud para principiantes 65/100
Los mantenedores suelen responder en 1 día
-
Dificultad 3/5 1-2 días Aptitud para principiantes 58/100
Los mantenedores suelen responder en 1 día
-
Dificultad 4/5 3-5 días Aptitud para principiantes 52/100
Los mantenedores suelen responder en 1 día
-
bug
Dificultad 4/5 3-5 días Aptitud para principiantes 48/100
Los mantenedores suelen responder en 1 día
Todos los issues de solidjs/solid
Issues similares
-
BeOS .json missingAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
mihaip/infinite-mac#475 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 83/100
marmelab/atomic-crm#401 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
Los mantenedores suelen responder en 1 día
-
enhancement good first issue
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
Deltares/webgl-streamline-visualizer#83 ·
Los mantenedores suelen responder en 1 día
-
ubicloud: usage is an awk syntax error on macOS, which also disables the vCPU quota preflightAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
Los mantenedores suelen responder en 1 día