From 32fda64a55b20b2c0636d394b2561f9f32623c33 Mon Sep 17 00:00:00 2001 From: Isaac Aderogba Date: Wed, 3 Jul 2024 10:37:34 +0100 Subject: [PATCH 1/3] feat: add MemoLazy function that recomputes value if selected value has changed --- package.json | 1 + src/main.ts | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ test/main.js | 45 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 test/main.js 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..ff403aa 100644 --- a/src/main.ts +++ b/src/main.ts @@ -24,4 +24,61 @@ 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) { + 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); + + if (keys1.length !== keys2.length) { + return false; + } + + for (let key of keys1) { + if (firstValue[key] !== secondValue[key]) { + return false; + } + } + return true; + } + + // 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}`) + } +}) From c0d759db3f0eba0e9330934df429525c5706a7ad Mon Sep 17 00:00:00 2001 From: Isaac Aderogba Date: Thu, 4 Jul 2024 09:09:03 +0100 Subject: [PATCH 2/3] refactor: simplify equals implementation --- src/main.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/main.ts b/src/main.ts index ff403aa..13e7913 100644 --- a/src/main.ts +++ b/src/main.ts @@ -67,16 +67,9 @@ function equals(firstValue: any, secondValue: any) { const keys1 = Object.keys(firstValue); const keys2 = Object.keys(secondValue); - if (keys1.length !== keys2.length) { - return false; - } - - for (let key of keys1) { - if (firstValue[key] !== secondValue[key]) { - return false; - } - } - return true; + return keys1.length === keys2.length && keys1.every(key => { + return firstValue[key] === secondValue[key] + }) } // otherwise just compare the values directly From 6cc889fa272e52e6369cc3686754b42d75c8cc73 Mon Sep 17 00:00:00 2001 From: Isaac Aderogba Date: Mon, 8 Jul 2024 09:07:08 +0100 Subject: [PATCH 3/3] feat: recursively evaluate equals to support deep comparison --- src/main.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main.ts b/src/main.ts index 13e7913..afec3c2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -58,20 +58,20 @@ export class MemoLazy { } } -function equals(firstValue: any, secondValue: any) { - const isFirstObject = typeof firstValue === 'object' && firstValue !== null; - const isSecondObject = typeof secondValue === 'object' && secondValue !== null +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 firstValue[key] === secondValue[key] + 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; + return firstValue === secondValue } \ No newline at end of file