Skip to content

Commit

Permalink
update @types/node, docs on allowStaleOnFetchAbort
Browse files Browse the repository at this point in the history
  • Loading branch information
isaacs committed Jun 1, 2023
1 parent df2451d commit 80ab1cd
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 15 deletions.
32 changes: 28 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,32 @@ Set to true to return a stale value from the cache when the
event, whether user-triggered, or due to internal cache behavior.

Unless `ignoreFetchAbort` is also set, the underlying
`fetchMethod` will still be considered canceled, and its return
value will be ignored and not cached.
`fetchMethod` will still be considered canceled, and any value
it returns will be ignored and not cached.

Caveat: since fetches are aborted when a new value is explicitly
set in the cache, this can lead to fetch returning a stale value,
since that was the fallback value _at the moment the `fetch()` was
initiated_, even though the new updated value is now present in
the cache.

For example:

```ts
const cache = new LRUCache<string, any>({
ttl: 100,
fetchMethod: async (url, oldValue, { signal }) => {
const res = await fetch(url, { signal })
return await res.json()
}
})
cache.set('https://example.com/', { some: 'data' })
// 100ms go by...
const result = cache.fetch('https://example.com/')
cache.set('https://example.com/', { other: 'thing' })
console.log(await result) // { some: 'data' }
console.log(cache.get('https://example.com/')) // { other: 'thing' }
```

### `ignoreFetchAbort`

Expand Down Expand Up @@ -569,7 +593,7 @@ For the usage of the `status` option, see **Status Tracking**
below.

If the value is `undefined`, then this is an alias for
`cache.delete(key)`. `undefined` is never stored in the cache.
`cache.delete(key)`. `undefined` is never stored in the cache.
See **Storing Undefined Values** below.

### `get(key, { updateAgeOnGet, allowStale, status } = {}) => value`
Expand Down Expand Up @@ -1028,7 +1052,7 @@ internally in a few places to indicate that a key is not in the
cache.

You may call `cache.set(key, undefined)`, but this is just an
an alias for `cache.delete(key)`. Note that this has the effect
an alias for `cache.delete(key)`. Note that this has the effect
that `cache.has(key)` will return _false_ after setting it to
undefined.

Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"repository": "git://github.com/isaacs/node-lru-cache.git",
"devDependencies": {
"@size-limit/preset-small-lib": "^7.0.8",
"@types/node": "^17.0.31",
"@types/node": "^20.2.5",
"@types/tap": "^15.0.6",
"benchmark": "^2.1.4",
"c8": "^7.11.2",
Expand Down
28 changes: 26 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -712,8 +712,32 @@ export namespace LRUCache {
* event, whether user-triggered, or due to internal cache behavior.
*
* Unless {@link OptionsBase.ignoreFetchAbort} is also set, the underlying
* {@link OptionsBase.fetchMethod} will still be considered canceled, and its return
* value will be ignored and not cached.
* {@link OptionsBase.fetchMethod} will still be considered canceled, and
* any value it returns will be ignored and not cached.
*
* Caveat: since fetches are aborted when a new value is explicitly
* set in the cache, this can lead to fetch returning a stale value,
* since that was the fallback value _at the moment the `fetch()` was
* initiated_, even though the new updated value is now present in
* the cache.
*
* For example:
*
* ```ts
* const cache = new LRUCache<string, any>({
* ttl: 100,
* fetchMethod: async (url, oldValue, { signal }) => {
* const res = await fetch(url, { signal })
* return await res.json()
* }
* })
* cache.set('https://example.com/', { some: 'data' })
* // 100ms go by...
* const result = cache.fetch('https://example.com/')
* cache.set('https://example.com/', { other: 'thing' })
* console.log(await result) // { some: 'data' }
* console.log(cache.get('https://example.com/')) // { other: 'thing' }
* ```
*/
allowStaleOnFetchAbort?: boolean

Expand Down
14 changes: 11 additions & 3 deletions test/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,8 @@ t.test('abort, but then keep on fetching anyway', async t => {
return new Promise(res =>
setTimeout(() => {
resolved = true
res(returnUndefined ? undefined : k)
if (returnUndefined) res()
else res(k)
}, 100)
)
},
Expand Down Expand Up @@ -706,7 +707,10 @@ t.test('allowStaleOnFetchAbort', async t => {
fetchMethod: async (k, _, { signal }) => {
return new Promise(res => {
const t = setTimeout(() => res(k), 100)
signal.addEventListener('abort', () => clearTimeout(t))
signal.addEventListener('abort', () => {
clearTimeout(t)
res()
})
})
},
})
Expand All @@ -716,7 +720,11 @@ t.test('allowStaleOnFetchAbort', async t => {
const p = c.fetch(1, { signal: ac.signal })
ac.abort(new Error('gimme the stale value'))
t.equal(await p, 10)
t.equal(c.get(1, { allowStale: true }), 10)
t.equal(c.get(1, { allowStale: true, noDeleteOnStaleGet: true }), 10)
const p2 = c.fetch(1)
c.set(1, 100)
t.equal(await p2, 10)
t.equal(c.get(1), 100)
})

t.test('background update on timeout, return stale', async t => {
Expand Down
1 change: 0 additions & 1 deletion tsconfig-base.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
"isolatedModules": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"inlineSources": true,
"strict": true,
Expand Down

0 comments on commit 80ab1cd

Please sign in to comment.