ef.js

Declarative DOM helper experiment


status: accepted

Use global context for building dependency graph

Context and Problem Statement

The Signals architecture heavily relies on mutable global context variable to store computation scope for building dependency graph and ownership tree. This is problematic in async and/or generator function, which run in other loops so the global variable had been changed at the time of a line inside the function body runs.

Decision Drivers

Considered Options

Manual scope management is excluded as it is already considered in Use Signals for reactive API.

Decision Outcome

Chosen option: “Keep relying on mutable global context”, because one can’t write useable and performant code using Algebraic Effect in the current state of JavaScript/TypeScript. This decision may be re-considered once JavaScript introduces an Algebraic Effect into the language or both JavaScript and TypeScript improves generator function syntax.

Pros and Cons of the Options

Keep relying on mutable global context

This option does not change traditional Signals style with work-around API.

const $age = signal(10);

effect((ctx) => {
  (async () => {
    const isLegalToDrink = derived((ctx) => $age.get(ctx) >= 20);

    await somePromise;

    console.log($isLegalToDrink.get(ctx));
  })();
});

Algebraic Effect using generator function

This option defines “lookup for dependant scope for the value” and “lookup for a scope owning this value or scope” as effects.

Effect handling is archived using generator function. This can be easily adopted to async code.

const $age = signal(10);

effect(async function* () {
  const $isLegalToDrink = yield derived(function* () {
    // `.get()` or `.value` is still necessary in order to distinguish value retrieval from
    // ownership lookup.
    return (yield $age.get()) >= 20;
  });

  await somePromise;

  console.log(yield $isLegalToDrink.get());
});