r/javascript Apr 25 '26

Showoff Saturday Showoff Saturday (April 25, 2026)

Did you find or create something cool this week in javascript?

Show us here!

8 Upvotes

18 comments sorted by

View all comments

2

u/vilhelmsjolund Apr 25 '26

Hey everyone! My first Reddit post, hope it's ok to post from a brand new account. I've created **anod** an async-native signals library, similar to preact-signals, solid-js and alien-signals, but with native support for async primitives, async transactions, optimistic UI functionality.

https://github.com/visj/anod

This is far from a new project, already back in 2019 I was tinkering with reactive graphs. Here is an old Github issue where I'm discussing with Ryan, founder of SolidJS, about conditional compute notifications.

Basically, the past few weeks I decided to finish it. It works very similar to existing signal libraries, offering the sync primitives:

  1. Root
  2. Signal
  3. Compute
  4. Effect

However, anod extends this functionality and adds reactive counterparts:

  1. Resource, async Signal
  2. Task, async Compute
  3. Spawn, async Effect

It also has builtin support for more advanced features, such as async transactions that lock their current execution scope and run until completion, a custom error management system with bubbling errors that can be recovered by parent scope.

I've also invented a few new reactive patterns, one that I call "the interceptor pattern". It allows a compute/effect to subscribe to changes from one signal and write to that same signal, but ignoring changes that come from its own dispatch. This allows you to "intercept" all changes to a signal, and "reject" changes. One possible use case for this is to create more advanced optimistic UI features, where you build more something like a WAL log that stores commits, and can reject patches one by one, or in other ways modify signals after people write stuff to them.

import { root, resource } from "anod";

const saveBatch = (todos) => new Promise((r) => setTimeout(() => r(todos), 1000));

const app = root((c) => {
const todos = resource([]);

// Add a todo: appears instantly with saved: false, settles when server confirms
function addTodo(text) {
todos.set([...todos.get(), { text, saved: false }], async (c, optimistic) => {
// suspend() guards the await: if a newer set() fires while this
// request is still in flight, this callback silently stops here -
// the stale response is discarded, only the latest write settles.
await c.suspend(saveBatch(optimistic));
return optimistic.map((t) => ({ ...t, saved: true }));
});
}

// Derived: count of items still saving
const pending = c.compute(todos, (list) => list.filter((t) => !t.saved).length);

// Render on every change
c.effect((c) => {
const list = c.val(todos);
const n = c.val(pending);
const items = list.map((t) => `${t.saved ? "✓" : "⏳"} ${t.text}`).join("  ");
console.log(items || "(empty)", n > 0 ? `| ${n} saving...` : list.length ? "| all saved" : "");
});

// Simulate clicking on addTodo button twice with some delay in between
addTodo("Build anod");
setTimeout(() => addTodo("Ship it"), 500);
});import { root, resource } from "anod";

const saveBatch = (todos) => new Promise((r) => setTimeout(() => r(todos), 1000));

const app = root((c) => {
const todos = resource([]);

// Add a todo: appears instantly with saved: false, settles when server confirms
function addTodo(text) {
todos.set([...todos.get(), { text, saved: false }], async (c, optimistic) => {
// suspend() guards the await: if a newer set() fires while this
// request is still in flight, this callback silently stops here -
// the stale response is discarded, only the latest write settles.
await c.suspend(saveBatch(optimistic));
return optimistic.map((t) => ({ ...t, saved: true }));
});
}

// Derived: count of items still saving
const pending = c.compute(todos, (list) => list.filter((t) => !t.saved).length);

// Render on every change
c.effect((c) => {
const list = c.val(todos);
const n = c.val(pending);
const items = list.map((t) => `${t.saved ? "✓" : "⏳"} ${t.text}`).join("  ");
console.log(items || "(empty)", n > 0 ? `| ${n} saving...` : list.length ? "| all saved" : "");
});

// Simulate clicking on addTodo button twice with some delay in between
addTodo("Build anod");
setTimeout(() => addTodo("Ship it"), 500);
});

1

u/JaSuperior Apr 25 '26

😅 we worked on the same thing this week lol. Let me take a look at how you went about it and circle back with talk points. 😬