synchronization-atom v2.0.2
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 the 
predicate, 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 areactionis provided. - If a 
reactionis provided, the call returns immediately, and when thepredicateis satisfied, thereactionis 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