omranjamal/synchronization-atom
A powerful zero-dependency primitive (written in TypeScript) to help build other synchronization primitives like locks, semaphores, events or barriers
synchronization-atom
A powerful (& typed) zero-dependency primitive to help build other synchronization primitives including but not limited to:
locks,
semaphores,
events,
barriers,
channels
This project was heavily inspired by mobx's
when
Installation
# pnpm
pnpm add --save synchronization-atomAPI
atom
function atom<T>(initialState: T): Atom<T>Creates and returns an Atom<T> with the given initialState: T.
async atom.conditionallyUpdate
interface Atom<T> {
...
conditionallyUpdate: (
predicate: (state: T) => boolean,
nextState: T | ((state: T) => T),
sideEffect?: (state: T) => void,
abortSignal?: AbortSignal
) => Promise<T>
...
}- Updates the atom's state to
nextStateif the
current state satisfies thepredicate. - If the current state
does not satisfy thepredicate, the call blocks until the
predicate is satisfied. - If a
sideEffectis provided, it is executed atomically
as part of the update (i.e. no other update or side-effect will be running
simultaneously against the atom). - Can be cancelled via an optional
AbortSignalas last argument.
async atom.waitFor
interface Atom<T> {
...
waitFor: (
predicate: (state: T) => boolean,
reaction?: (state: T) => void,
abortSignal?: AbortSignal
) => Promise<T> | void
...
}- Blocks until the atom's state satisfies the
predicate, unless a
reactionis provided. - If a
reactionis provided, the call returns
immediately, and when thepredicateis satisfied, thereaction
is executed. - Can be cancelled via an optional
AbortSignalas last argument.
atom.getState
interface Atom<T> {
...
getState: () => T
...
}Returns the current state of the atom.
Usage Examples
Make a lock
import {atom} from 'synchronization-atom';
const lockAtom = atom(false /* is locked */);
async function test(n: number) {
// aquire lock
await lockAtom.conditionallyUpdate(
(locked) => locked === false,
true
);
console.log(n, `aquired lock`);
await doCrazyAsyncStuffHere();
console.log(n, `releasing lock`);
// release lock
lockAtom.conditionallyUpdate(() => true, false);
}
Promise.all([test(1), test(2), test(3)]);Make a semaphore
import { atom } from 'synchronization-atom';
const semaphoreAtom = atom(3 /* no. of seats */);
async function test(n: number) {
// aquire lock
await semaphoreAtom.conditionallyUpdate(
(seats) => seats > 0,
(seats) => seats - 1
);
console.log(n, `aquired lock`);
await doCrazyAsyncStuffHere();
console.log(n, `releasing lock`);
// release lock
semaphoreAtom.conditionallyUpdate(
() => true,
seats => seats + 1
);
}
Promise.all([test(1), test(2), test(3), test(4), test(5)]);Make a event
import { atom } from 'synchronization-atom';
const eventAtom = atom(false /* is event set */);
async function test(n: number) {
console.log(n, `waiting for event`);
await eventAtom.waitFor((isSet) => isSet === true);
console.log(n, `running`);
}
Promise.all([test(1), test(2), test(3)]);
console.log(`setting event`);
eventAtom.conditionallyUpdate(() => true, true);Make a barrier
import { atom } from 'synchronization-atom';
const barrierAtom = atom(3 /* empty seats */);
async function test(n: number) {
await barrierAtom.conditionallyUpdate(
() => true,
(emptySeats) => emptySeats - 1
);
console.log(n, `waiting for seats to fill`);
await barrierAtom.waitFor((emptySeats) => emptySeats < 0);
console.log(n, `running`);
}
Promise.all([test(1), test(2), test(3), test(4), test(5)]);Why?
I often use async calls like separate threads or at least like
Go routines, as in as long as I'm fetching from DB or API over
a network, it is effectively multi-threading (at least in my head).Sadly I couldn't enjoy the very powerful sync primitives that
Python,
Java
or Go has to offer.Simultaneously, I noticed different standard libraries of the different
languages have a different set of sync primitives but mutexes were
at the heart.I set out to create these primitives for JS while basing them off
of a single primitive that is analogous to a mutex, but on parr
with the level of expressiveness and ease we come to expect from
the JS ecosystem.synchronization-atom is the result of that effort.
License
MIT