Skip to content

Commit

Permalink
Added callback option to manually check for a stale value
Browse files Browse the repository at this point in the history
  • Loading branch information
mupperton committed Apr 10, 2023
1 parent 7b54bc3 commit 97b4d85
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ Optionally pass a configuration object as a second argument to the constructor,
* `allowUndefinedInitialValue`: Optional, defaults to `false`, set to `true` if you want to manually set an `initialValue` of `undefined`, otherwise only defined values will be used.
* `cacheRejections`: Optional, defaults to `false`, set to `true` for your Phunky instance to cache any errors that are thrown by your function. If `false`, calling `.current()` will try re-invoking your function if it previously rejected.
* `ttl`: (time-to-live) Optional. If set, it must be a positive integer for the number of **milliseconds** resolved values should live for. Any calls to `.current()` after that time, will automatically re-invoke your function to get an updated value.
* `stale`: Optional. A callback that takes the current resolved value as an argument to manually check if that value should be considered stale. The callback should return `true` if stale. This will cause your Phunk to automatically resolve a new value.

### Class methods

Expand Down
47 changes: 45 additions & 2 deletions src/phunk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ describe('caching', () => {
counter += 1
return counter
})

const ttl = 100
const phunk = new Phunk(resolver, { ttl })

Expand All @@ -204,7 +204,7 @@ describe('caching', () => {
counter += 1
return counter
})

const ttl = 100
const phunk = new Phunk(resolver, { ttl })

Expand All @@ -229,6 +229,49 @@ describe('caching', () => {
expect(resolver).toHaveBeenCalledTimes(1)
})
})

describe('manual stale check', () => {
it.each([
[false, false],
[1, false],
[true, true],
])('cache is stale when the stale method returns true', async (staleResult, shouldBeStale) => {
const resolver = jest.fn(() => 1)
const stale = () => staleResult as boolean

const phunk = new Phunk(resolver, { stale })

await phunk.current()
await phunk.current()

expect(resolver).toHaveBeenCalledTimes(shouldBeStale ? 2 : 1)
})

it('stale checks can be async', async () => {
const resolver = jest.fn(() => 1)
const stale = async () => {
await sleep(100)
return true
}

const phunk = new Phunk(resolver, { initialValue: 0, stale })

await phunk.current()

expect(resolver).toHaveBeenCalledTimes(1)
})

it.each([
[0, 1],
[2, 2],
])('stale checks receive the currently resolved value', async (initialValue, expected) => {
const resolver = () => 1

const phunk = new Phunk(resolver, { initialValue, stale: current => current === 0 })

await expect(phunk.current()).resolves.toBe(expected)
})
})
})

describe('resolver status', () => {
Expand Down
14 changes: 14 additions & 0 deletions src/phunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ type Config<T> = {
allowUndefinedInitialValue?: boolean
cacheRejections?: boolean
ttl?: number
stale?: (currentResolvedValue?: T) => boolean | Promise<boolean>
}

const noop = () => {}
Expand All @@ -20,6 +21,8 @@ class Phunk<T> {
#cacheRejections
#ttl
#lastResolveTime = 0
#currentResolvedValue?: T
#stale: ((currentResolvedValue?: T) => boolean | Promise<boolean>) | null = null

constructor(fn: () => T | Promise<T>, config?: Config<T>) {
const options = config ?? {}
Expand All @@ -34,8 +37,13 @@ class Phunk<T> {

this.#ttl = options.ttl ?? null

if (typeof options.stale === 'function') {
this.#stale = options.stale
}

if (typeof options.initialValue !== 'undefined') {
this.#promise = Promise.resolve(options.initialValue)
this.#currentResolvedValue = options.initialValue
this.#isResolved = true
} else if (Object.hasOwnProperty.call(options, 'initialValue') && options.allowUndefinedInitialValue === true) {
this.#promise = Promise.resolve(undefined as T)
Expand Down Expand Up @@ -64,6 +72,10 @@ class Phunk<T> {
// ttl expired
return this.next()
}
if (this.#stale !== null && await this.#stale(this.#currentResolvedValue) === true) {
// current resolved value is stale
return this.next()
}

return this.#promise
}
Expand All @@ -82,9 +94,11 @@ class Phunk<T> {

try {
const result = await this.#fn()
this.#currentResolvedValue = result
this.#isResolved = true
return result
} catch (error) {
this.#currentResolvedValue = undefined
this.#isRejected = true
throw error
} finally {
Expand Down

0 comments on commit 97b4d85

Please sign in to comment.