UNPKG

@traxjs/trax

Version:

Reactive state management

155 lines (135 loc) 5.53 kB
import { ProcessingContext } from "./types"; type SyncFunction = (...args: any[]) => any; type AsyncGenFunction = (...args: any[]) => Generator<Promise<any>, void, any>; function noop() { } /** * Wrap sync and generator-based async functions to produce an event stream processing context * @param fn the function to wrap * @param startProcessingContext a function that should start and return the processing context * @param error a function that will be called when errors must be raised * @param onProcessStart [optional] a callback called a the beginning of each processing step. Will stop the processing if it return false * @param onProcessEnd [optional] a callback called at the end of each processing step * @returns */ export function wrapFunction<F extends SyncFunction | AsyncGenFunction>( fn: F, startProcessingContext: () => ProcessingContext, error: (msg: any) => void, onProcessStart: () => false | void = noop, onProcessEnd: (done: boolean) => void = noop, ): ReturnType<F> extends Generator ? (...args: Parameters<F>) => Promise<any> : (...args: Parameters<F>) => ReturnType<F> { /** processing context associated to the wrapped function */ let processingContext: ProcessingContext | null = null; /** Generator returned by the wrapped function in case of async functions */ let generator: Generator<Promise<any>, void, any> | undefined; let generatorPromise: Promise<any> | undefined = undefined; let genPromiseResolve: ((v: any) => void) | undefined; let genPromiseReject: ((v: any) => void) | undefined; return (...args: any[]) => { onProcessStart(); processingContext = startProcessingContext(); let itr: IteratorResult<Promise<any>, void> | undefined; let done = true; generator = undefined; let v: any = undefined; try { // compute will indirectly call registerDependency() through proxy getters v = fn(...args); if (!!v && v.next && typeof v.next === "function") { generator = v; // get the first iterator value itr = v.next(); done = !!itr!.done; } else if (!!v && typeof v === "object" && typeof v.then === "function" && typeof v.catch === "function") { // catch promise errors v.catch((err: any) => { error(err); }); } } catch (ex) { error(ex); } onProcessEnd(done); if (done) { processingContext.end(); processingContext = null; } else { processingContext.pause(); processIteratorPromise(itr, generator!); } if (generator !== undefined) { if (generatorPromise) { if (genPromiseReject) { genPromiseReject("Processing Cancelled"); } } generatorPromise = genPromiseResolve = genPromiseReject = undefined; if (done) { return Promise.resolve(v); } else { // we create a promise that will resolve when the generator is done generatorPromise = new Promise((resolve, reject) => { genPromiseResolve = resolve; genPromiseReject = reject; }); } return generatorPromise; } return v as any; } function processIteratorPromise(itr: IteratorResult<Promise<any>, void> | undefined, g: Generator<Promise<any>>) { if (itr && !itr.done) { const yieldValue = itr.value; const logError = (err: any) => { error(err); } if (!!yieldValue && (yieldValue as any).then) { // yield returned a promise yieldValue.then((v) => { computeNext(v, g); }).catch(logError); } else { // yield did not return a promise - let's create one Promise.resolve().then(() => { computeNext(yieldValue, g); }).catch(logError); } } } function computeNext(nextValue: any, g: Generator<Promise<any>, void, any>) { if (g !== generator || !processingContext) return; // note: g !==generator when compute() was called before the previous generator processing ended if (onProcessStart() === false) { resetGenPromise(); return; // caller is disposed } processingContext.resume(); let itr: IteratorResult<Promise<any>, void> | undefined; let done = true; try { // compute will indirectly call registerDependency() through proxy getters itr = g.next(nextValue); done = !!itr.done; } catch (ex) { error(ex); done = true; if (genPromiseReject) { genPromiseReject(ex); } resetGenPromise(); } onProcessEnd(done); if (done) { processingContext.end(); if (genPromiseResolve) { genPromiseResolve(itr!.value); } } else { processingContext.pause(); processIteratorPromise(itr, g); } } function resetGenPromise() { genPromiseResolve = genPromiseReject = undefined; } }