Skip to content

Commit

Permalink
Allow passing an AbortSignal to cache.fetch()
Browse files Browse the repository at this point in the history
fix: #273
  • Loading branch information
isaacs committed Feb 21, 2023
1 parent f764a81 commit 4218b2c
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 5 deletions.
29 changes: 26 additions & 3 deletions README.md
Expand Up @@ -508,14 +508,28 @@ can be confusing when setting values specifically to `undefined`,
as in `cache.set(key, undefined)`. Use `cache.has()` to
determine whether a key is present in the cache at all.

### `async fetch(key, { updateAgeOnGet, allowStale, size, sizeCalculation, ttl, noDisposeOnSet, forceRefresh } = {}) => Promise`
### `async fetch(key, options = {}) => Promise`

The following options are supported:

* `updateAgeOnGet`
* `allowStale`
* `size`
* `sizeCalculation`
* `ttl`
* `noDisposeOnSet`
* `forceRefresh`
* `signal` - AbortSignal can be used to cancel the `fetch()`
* `fetchContext` - sets the `context` option passed to the
underlying `fetchMethod`.

If the value is in the cache and not stale, then the returned
Promise resolves to the value.

If not in the cache, or beyond its TTL staleness, then
`fetchMethod(key, staleValue, options)` is called, and the value
returned will be added to the cache once resolved.
`fetchMethod(key, staleValue, { options, signal, context })` is
called, and the value returned will be added to the cache once
resolved.

If called with `allowStale`, and an asynchronous fetch is
currently in progress to reload a stale value, then the former
Expand All @@ -539,6 +553,15 @@ When the fetch method resolves to a value, if the fetch has not
been aborted due to deletion, eviction, or being overwritten,
then it is added to the cache using the options provided.

If the key is evicted or deleted before the `fetchMethod`
resolves, then the AbortSignal passed to the `fetchMethod` will
receive an `abort` event, and the promise returned by `fetch()`
will reject with the reason for the abort.

If a `signal` is passed to the `fetch()` call, then aborting the
signal will abort the fetch and cause the `fetch()` promise to
reject with the reason provided.

### `peek(key, { allowStale } = {}) => value`

Like `get()` but doesn't update recency or delete stale items.
Expand Down
1 change: 1 addition & 0 deletions index.d.ts
Expand Up @@ -645,6 +645,7 @@ declare namespace LRUCache {
interface FetchOptions<K, V> extends FetcherFetchOptions<K, V> {
forceRefresh?: boolean
fetchContext?: any
signal?: AbortSignal
}

interface FetcherOptions<K, V> {
Expand Down
5 changes: 5 additions & 0 deletions index.js
Expand Up @@ -742,6 +742,11 @@ class LRUCache {
return v
}
const ac = new AC()
if (options.signal) {
options.signal.addEventListener('abort', () =>
ac.abort(options.signal.reason)
)
}
const fetchOpts = {
signal: ac.signal,
options,
Expand Down
35 changes: 34 additions & 1 deletion test/fetch.ts
Expand Up @@ -68,7 +68,7 @@ t.test('asynchronous fetching', async t => {
t.matchSnapshot(JSON.stringify(dump), 'safe to stringify dump')

t.equal(e.isBackgroundFetch(v), true)
t.equal(e.backgroundFetch('key', 0), v)
t.equal(e.backgroundFetch('key', 0, {}), v)
await v
const v7 = await c.fetch('key', {
allowStale: true,
Expand Down Expand Up @@ -626,3 +626,36 @@ t.test(
t.equal([...c].length, 2)
}
)

t.test('send a signal', async t => {
let aborted: Error | undefined = undefined
let resolved: boolean = false
const c = new LRU<number, number>({
max: 10,
fetchMethod: async (k, _, { signal }) => {
signal.addEventListener('abort', () => {
aborted = signal.reason
})
return new Promise(res =>
setTimeout(() => {
resolved = true
res(k)
}, 100)
)
},
})
const ac = new AbortController()
const p = c.fetch(1, { signal: ac.signal })
const er = new Error('custom abort signal')
const testp = t.rejects(p, er)
ac.abort(er)
await testp
t.equal(
resolved,
false,
'should have aborted before fetchMethod resolved'
)
t.equal(aborted, er)
t.equal(ac.signal.reason, er)
t.equal(c.get(1), undefined)
})
7 changes: 6 additions & 1 deletion test/fixtures/expose.ts
Expand Up @@ -8,7 +8,12 @@ export const exposeStatics = (LRU: typeof LRUCache) => {
export const expose = (cache: LRUCache<any, any>) => {
return cache as unknown as {
isBackgroundFetch: (v: any) => boolean
backgroundFetch: (v: any, index: number) => Promise<any>
backgroundFetch: (
v: any,
index: number,
options: { [k: string]: any },
context?: any
) => Promise<any>
valList: any[]
keyList: any[]
free: number[]
Expand Down

0 comments on commit 4218b2c

Please sign in to comment.