Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add MemoLazy function that recomputes value if selected value has changed #6

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
],
"scripts": {
"compile": "tsc",
"test": "tsc && node ./test/main.js",
"release": "pnpm compile && pnpm publish"
},
"devDependencies": {
Expand Down
50 changes: 50 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,54 @@ export class Lazy<T> {
this._value = value
this.creator = null
}
}

export class MemoLazy<S, V> {
private selected: S | undefined = undefined
private _value: Promise<V> | undefined = undefined

constructor(
private selector: () => S,
private creator: (selected: S) => Promise<V>
) {}

get hasValue() {
return this._value !== undefined
}

get value(): Promise<V> {
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<V>) {
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
}
45 changes: 45 additions & 0 deletions test/main.js
Original file line number Diff line number Diff line change
@@ -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}`)
}
})