politie / sherlock

A reactive programming library for JavaScript applications, built with TypeScript.
Apache License 2.0
39 stars 12 forks source link

Support async initialisation of Derivables (sherlock-async) #58

Closed pavadeli closed 6 years ago

pavadeli commented 6 years ago

Sherlocks derivables are synchronous. That is by design and the best selling point of the library. However, sometimes, we want to use the power of derivables (including dependency tracking and glitch-free derivations) using data that originates from an asynchronous source.

For this, I suggest we add a sherlock-async extension that adds the classes AsyncDerivable and AsyncAtom. These classes will allow to write the same code you are used to (synchronous looking and with automatic dependency tracking), but it will take into account the asynchronous nature of the data-source.

// Augmented Derivable interface
interface Derivable<V>  {
    // Going from synchronous to asynchronous is free. See AsyncDerivable#fallbackTo for
    // the reverse operation.
    async(): AsyncDerivable<V>;
}

// AsyncDerivable is the asynchronous variant of Derivable. It works the same, but it
// can handle an "uncertainty period" in which the data is not ready yet. This can be handled
// in all the known derivable operations and the derivable will maintain this uncertain state
// until all required data is present. Only then, the first reactor will fire.
interface AsyncDerivable<V> extends Derivable<V> {
    // All derivations on Async* return Async*, such as:
    pluck<K extends keyof V>(k: K): AsyncDerivable<V[K]>;
    derive<R>(f: (v: V) => R): AsyncDerivable<R>;
    // ...

    // To return to synchronous world you should give a fallback that will be used as value
    // during the uncertainty periods.
    fallbackTo<S>(v: S | Derivable<S>): Derivable<V | S>;

    // The toPromise works like toObservable, but it automatically handles the uncertain period and will 
    // by default resolve only when the data is available. Of course this is customizable using the
    // options Object.
    toPromise(options?: Partial<ReactorOptions<V>>): Promise<V>;
}

// Augmented Atom interface
interface Atom<V> extends Derivable<V> {
    async(): AsyncAtom<V>;
}

// AsyncAtom is the settable cousin of AsyncDerivable.
interface AsyncAtom<V> extends Atom<V>, AsyncDerivable<V> {
    // All variants of set operations are supported, but can now be asynchronous.
    set(v: V): Promise<void>;
    pluck<K extends keyof V>(k: K): AsyncAtom<V[K]>;
    swap(f: (v: V) => V | Promise<V>): Promise<void>;
    // ...
}