diff --git a/package.json b/package.json index aec80f9..d477e7c 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ ], "scripts": { "compile": "tsc", + "test": "tsc && node ./test/main.js", "release": "pnpm compile && pnpm publish" }, "devDependencies": { diff --git a/src/main.ts b/src/main.ts index 54f7eb1..afec3c2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -24,4 +24,54 @@ export class Lazy { this._value = value this.creator = null } +} + +export class MemoLazy { + private selected: S | undefined = undefined + private _value: Promise | undefined = undefined + + constructor( + private selector: () => S, + private creator: (selected: S) => Promise + ) {} + + get hasValue() { + return this._value !== undefined + } + + get value(): Promise { + const selected = this.selector() + if (this._value !== undefined && equals(this.selected, selected)) { + // value exists and selected hasn't changed, so return the cached value + return this._value + } + + this.selected = selected + const result = this.creator(selected) + this.value = result + + return result + } + + set value(value: Promise) { + this._value = value + } +} + +function equals(firstValue: any, secondValue: any): boolean { + const isFirstObject = typeof firstValue === "object" && firstValue !== null + const isSecondObject = typeof secondValue === "object" && secondValue !== null + + // do a shallow comparison of objects, arrays etc. + if (isFirstObject && isSecondObject) { + const keys1 = Object.keys(firstValue) + const keys2 = Object.keys(secondValue) + + return keys1.length === keys2.length && keys1.every((key) => { + return equals(firstValue[key], secondValue[key]) + }) + } + + // otherwise just compare the values directly + return firstValue === secondValue } \ No newline at end of file diff --git a/test/main.js b/test/main.js new file mode 100644 index 0000000..da3a117 --- /dev/null +++ b/test/main.js @@ -0,0 +1,45 @@ +const { Lazy, MemoLazy } = require("../out/main") + +const tests = [] +const it = (message, test) => tests.push({ message, test }) +const assert = (condition) => { + if (!condition) throw new Error() +} + +it("[Lazy] reuses the created value even if the selected value has changed", () => { + let selectedValue = 0 + const lazy = new Lazy(() => { + return selectedValue * 10 + }) + + selectedValue++ + assert(lazy.value === 10) + + selectedValue++ + assert(lazy.value === 10) +}) + +it("[MemoLazy] recomputes the created value if the selected value has changed", () => { + let selectedValue = 0 + const lazy = new MemoLazy( + () => selectedValue, + (selectedValue) => { + return selectedValue * 10 + } + ) + + selectedValue++ + assert(lazy.value === 10) + + selectedValue++ + assert(lazy.value === 20) +}) + +tests.forEach((test) => { + try { + test.test() + console.log(`✅ ${test.message}`) + } catch (error) { + console.error(`❌ ${test.message}`) + } +})