Skip to content

Commit

Permalink
Added option for an initial value
Browse files Browse the repository at this point in the history
  • Loading branch information
mupperton committed Nov 6, 2022
1 parent 026e552 commit 418e19c
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 3 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ console.log(myThunk.isRejected()) // false
Optionally pass a configuration object as a second argument to the constructor, with the following options

* `init`: Optional, defaults to `false`, set to `true` to immediately invoke your function and have the result ready for when you first call `.current()`.
* `initialValue`: Optional. If set, your Phunk instance will resolve this value until either you call `.next()` or the `tll` duration has elapsed. Note, this option is redundant if you set `init` to true.
* `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.

Expand Down
53 changes: 52 additions & 1 deletion src/phunk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,48 @@ describe('immediately invoke', () => {

it('does not throw unhandled exception errors if resolver throws', () => {
const mockError = new Error('Rejection')
const resolver = jest.fn(() => { throw mockError })
const resolver = () => { throw mockError }

expect(() => new Phunk(resolver, { init: true })).not.toThrow(mockError)
})
})

describe('initial value', () => {
it('can be configured to have an initial value', async () => {
const resolver = jest.fn(() => 2)

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

await expect(phunk.current()).resolves.toBe(1)

expect(resolver).toHaveBeenCalledTimes(0)
})

it('does not use undefined as an initial value by default', async () => {
const resolver = () => 1

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

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

it('can be configured to allow an initial value of undefined', async () => {
const resolver = () => 1

const phunk = new Phunk(resolver, { initialValue: undefined, allowUndefinedInitialValue: true })

await expect(phunk.current()).resolves.toBeUndefined()
})

it('never sets the initial value if not defined', async () => {
const resolver = () => 1

const phunk = new Phunk(resolver, { allowUndefinedInitialValue: true })

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

describe('caching', () => {
it('caches resolved values', async () => {
const resolver = jest.fn(() => 1)
Expand Down Expand Up @@ -177,6 +213,21 @@ describe('caching', () => {

expect(resolver).toHaveBeenCalledTimes(2)
})

it('initial values also have the ttl', async () => {
const resolver = jest.fn(() => 2)

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

await expect(phunk.current()).resolves.toBe(1)

await sleep(ttl)

await expect(phunk.current()).resolves.toBe(2)

expect(resolver).toHaveBeenCalledTimes(1)
})
})
})

Expand Down
16 changes: 14 additions & 2 deletions src/phunk.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
interface Config {
interface Config<T> {
init?: boolean
initialValue?: T
allowUndefinedInitialValue?: boolean
cacheRejections?: boolean
ttl?: number
}
Expand All @@ -20,7 +22,7 @@ class Phunk<T> {
#ttl
#lastResolve = 0

constructor(fn: () => T | Promise<T>, config?: Config) {
constructor(fn: () => T | Promise<T>, config?: Config<T>) {
const options = config ?? {}

if (typeof options.ttl !== 'undefined' && (!Number.isSafeInteger(options.ttl) || options.ttl < 0)) {
Expand All @@ -33,6 +35,16 @@ class Phunk<T> {

this.#ttl = options.ttl ?? null

if (typeof options.initialValue !== 'undefined') {
this.#promise = Promise.resolve(options.initialValue)
} else if (Object.hasOwnProperty.call(options, 'initialValue') && options.allowUndefinedInitialValue === true) {
this.#promise = Promise.resolve(undefined as T)
}

if (this.#promise !== null && this.#ttl !== null) {
this.#lastResolve = Date.now()
}

if (options.init === true) {
this.next().catch(noop) // catch error to avoid unhandled promise exceptions
}
Expand Down

0 comments on commit 418e19c

Please sign in to comment.