From ecc1719ec006756c13d93a205edeb31d8d92b852 Mon Sep 17 00:00:00 2001 From: Paco van der Linden Date: Wed, 31 Aug 2022 09:30:36 +0200 Subject: [PATCH] feat(sherlock-utils): add new `parallelStruct` util (#37) The new `parallelStruct` util does the same as the existing `struct` util, the only difference being that it activates the nested Derivables in parallel (eagerly). --- libs/sherlock-utils/src/lib/struct.test.ts | 27 ++++++++---- libs/sherlock-utils/src/lib/struct.ts | 50 +++++++++++++++++++--- 2 files changed, 62 insertions(+), 15 deletions(-) diff --git a/libs/sherlock-utils/src/lib/struct.test.ts b/libs/sherlock-utils/src/lib/struct.test.ts index ab62896..38d996a 100644 --- a/libs/sherlock-utils/src/lib/struct.test.ts +++ b/libs/sherlock-utils/src/lib/struct.test.ts @@ -1,7 +1,18 @@ import { atom } from '@skunkteam/sherlock'; -import { struct } from './struct'; +import { parallelStruct, struct } from './struct'; + +describe.each([ + [struct, 'serial'], + [parallelStruct, 'parallel'], +])('sherlock-utils/%p', (fn, mode) => { + test(`should activate derivables in ${mode}`, () => { + const parts = [1, 2, 3].map(() => atom.unresolved()); + const result = fn(parts).autoCache(); + + expect(result.resolved).toBeFalse(); + expect(parts.map(d$ => d$.connected)).toEqual([true, mode === 'parallel', mode === 'parallel']); + }); -describe('sherlock-utils/struct', () => { it('should copy any value-type as-is', () => { const obj = { date: new Date(), @@ -9,14 +20,14 @@ describe('sherlock-utils/struct', () => { string: 'asdf', strings: ['asdf', 'sdfg'], }; - const result = struct(obj).get(); + const result = fn(obj).get(); expect(result).toEqual(obj); expect(result.date).toBe(obj.date); }); it('should return a Derivables as is', () => { const a = atom(123); - expect(struct(a)).toBe(a); + expect(fn(a)).toBe(a); }); it('should turn an array of derivables into an unwrapped derivable', () => { @@ -25,7 +36,7 @@ describe('sherlock-utils/struct', () => { const number3$ = number1$.derive(n => n + number2$.get()); const number$s = [number1$, number2$, number3$]; - const numbers$ = struct(number$s); + const numbers$ = fn(number$s); expect(numbers$.get()).toEqual([1, 2, 3]); @@ -37,7 +48,7 @@ describe('sherlock-utils/struct', () => { const name$ = atom('Edwin'); const tel$ = atom('0612345678'); const person = { name: name$, tel: tel$ }; - const person$ = struct(person); + const person$ = fn(person); expect(person$.get()).toEqual({ name: 'Edwin', tel: '0612345678' }); @@ -62,7 +73,7 @@ describe('sherlock-utils/struct', () => { }, ], }; - const nested$ = struct(obj).autoCache(); + const nested$ = fn(obj).autoCache(); expect(nested$.get()).toEqual({ name: 'Edwin', @@ -96,7 +107,7 @@ describe('sherlock-utils/struct', () => { const readonlyObject = { a$, b$ } as const; const array = [a$, b$]; const readonlyArray = [a$, b$] as const; - const d$ = struct({ object, readonlyObject, array, readonlyArray }); + const d$ = fn({ object, readonlyObject, array, readonlyArray }); const result = d$.get(); diff --git a/libs/sherlock-utils/src/lib/struct.ts b/libs/sherlock-utils/src/lib/struct.ts index ca53587..3f157d9 100644 --- a/libs/sherlock-utils/src/lib/struct.ts +++ b/libs/sherlock-utils/src/lib/struct.ts @@ -1,4 +1,4 @@ -import { Derivable, derive, isDerivable, utils } from '@skunkteam/sherlock'; +import { Derivable, derive, isDerivable, unresolved, utils } from '@skunkteam/sherlock'; /** * Converts a map or array of Derivables or any nested structure containing maps, arrays and Derivables into a single @@ -12,23 +12,59 @@ import { Derivable, derive, isDerivable, utils } from '@skunkteam/sherlock'; * * It only touches Arrays, plain Objects and Derivables, the rest is simply returned inside the Derivable as-is. * - * @param obj the object to deepunwrap into a derivable + * This function unwraps Derivables one by one, waiting for a Derivable to become resolved before looking for the next + * Derivable. + * + * @param input the object to deepunwrap into a derivable */ export function struct(input: T): Derivable> { - return (isDerivable(input) ? input : derive(deepUnwrap, input)) as Derivable>; + return (isDerivable(input) ? input : derive(deepUnwrapSerial, input)) as Derivable>; +} + +/** + * Converts a map or array of Derivables or any nested structure containing maps, arrays and Derivables into a single + * Derivable with all nested Derivables unwrapped into it. + * + * ```typescript + * const obj = { key1: atom(123), key2: atom(456) }; + * const obj$ = struct(obj); + * expect(obj$.get()).to.deep.equal({ key1: 123, key2: 456 }); + * ``` + * + * It only touches Arrays, plain Objects and Derivables, the rest is simply returned inside the Derivable as-is. + * + * This function eagerly activates Derivables in the input, so they get activated ("connected") in parallel. + * + * @param input the object to deepunwrap into a derivable + */ +export function parallelStruct(input: T): Derivable> { + return (isDerivable(input) ? input : derive(deepUnwrapParallel, input)) as Derivable>; +} + +function deepUnwrapSerial(obj: any) { + return deepUnwrap(obj, d$ => d$.get()); +} + +function deepUnwrapParallel(obj: any) { + let resolved = true; + const value = deepUnwrap(obj, d$ => { + resolved &&= d$.resolved; + return d$.value; + }); + return resolved ? value : unresolved; } -function deepUnwrap(obj: any): any { +function deepUnwrap(obj: any, handleDerivable: (d$: Derivable) => unknown): any { if (isDerivable(obj)) { - return obj.get(); + return handleDerivable(obj); } if (Array.isArray(obj)) { - return obj.map(deepUnwrap); + return obj.map(element => deepUnwrap(element, handleDerivable)); } if (utils.isPlainObject(obj)) { const result: Record = {}; for (const key of Object.keys(obj)) { - result[key] = deepUnwrap(obj[key]); + result[key] = deepUnwrap(obj[key], handleDerivable); } return result; }