Skip to content

Commit 3d1c0ee

Browse files
committed
Preserve fetch promises working in the background
Fix: #389
1 parent 8b04fbe commit 3d1c0ee

2 files changed

Lines changed: 76 additions & 4 deletions

File tree

src/index.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2342,6 +2342,8 @@ export class LRUCache<K extends {}, V extends {}, FC = unknown> {
23422342
const cb = (v: V | undefined, updateCache = false): V | undefined => {
23432343
const { aborted } = ac.signal
23442344
const ignoreAbort = options.ignoreFetchAbort && v !== undefined
2345+
const proceed = options.ignoreFetchAbort ||
2346+
!!(options.allowStaleOnFetchAbort && v !== undefined)
23452347
if (options.status) {
23462348
if (aborted && !updateCache) {
23472349
options.status.fetchAborted = true
@@ -2352,7 +2354,7 @@ export class LRUCache<K extends {}, V extends {}, FC = unknown> {
23522354
}
23532355
}
23542356
if (aborted && !ignoreAbort && !updateCache) {
2355-
return fetchFail(ac.signal.reason)
2357+
return fetchFail(ac.signal.reason, proceed)
23562358
}
23572359
// either we didn't abort, and are still here, or we did, and ignored
23582360
const bf = p as BackgroundFetch<V>
@@ -2380,10 +2382,11 @@ export class LRUCache<K extends {}, V extends {}, FC = unknown> {
23802382
options.status.fetchRejected = true
23812383
options.status.fetchError = er
23822384
}
2383-
return fetchFail(er)
2385+
// do not pass go, do not collect $200
2386+
return fetchFail(er, false)
23842387
}
23852388

2386-
const fetchFail = (er: any): V | undefined => {
2389+
const fetchFail = (er: any, proceed: boolean): V | undefined => {
23872390
const { aborted } = ac.signal
23882391
const allowStaleAborted = aborted && options.allowStaleOnFetchAbort
23892392
const allowStale =
@@ -2393,7 +2396,8 @@ export class LRUCache<K extends {}, V extends {}, FC = unknown> {
23932396
if (this.#valList[index as Index] === p) {
23942397
// if we allow stale on fetch rejections, then we need to ensure that
23952398
// the stale value is not removed from the cache when the fetch fails.
2396-
const del = !noDelete || bf.__staleWhileFetching === undefined
2399+
const del = !noDelete ||
2400+
!proceed && bf.__staleWhileFetching === undefined
23972401
if (del) {
23982402
this.#delete(k, 'fetch')
23992403
} else if (!allowStaleAborted) {

test/lost-background-fetch.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// https://github.com/isaacs/node-lru-cache/issues/389
2+
import t from 'tap'
3+
4+
const clock = t.clock
5+
clock.enter()
6+
7+
const { LRUCache } = await import('../src/index.js')
8+
9+
const c = new LRUCache<number, number, void>({
10+
ttl: 1000,
11+
max: 10,
12+
ignoreFetchAbort: true,
13+
allowStaleOnFetchAbort: true,
14+
fetchMethod: async k => {
15+
return new Promise(res => {
16+
setTimeout(() => {
17+
res(k)
18+
}, 100 * k)
19+
})
20+
},
21+
})
22+
23+
// c.set(1, 10)
24+
// c.set(2, 20)
25+
26+
// make them stale
27+
// clock.advance(2000)
28+
29+
//t.ok(c.getRemainingTTL(1) < 0, {
30+
// found: c.getRemainingTTL(1),
31+
// wanted: '< 0',
32+
//})
33+
//t.ok(c.getRemainingTTL(2) < 0, {
34+
// found: c.getRemainingTTL(2),
35+
// wanted: '< 0',
36+
//})
37+
38+
const ac = new AbortController()
39+
const ac2 = new AbortController()
40+
// fails
41+
const p2 = c.fetch(2, { signal: ac.signal })
42+
const p1 = c.fetch(1, { signal: ac.signal })
43+
44+
//c.set(3, 3)
45+
46+
// works
47+
// const p1 = c.fetch(1, { signal: ac.signal })
48+
// const p2 = c.fetch(2, { signal: ac.signal })
49+
50+
clock.advance(50)
51+
await new Promise<void>(res => queueMicrotask(res))
52+
ac.abort(new Error('gimme the stale value'))
53+
ac2.abort(new Error('gimme the stale value 2'))
54+
t.equal(await p1, undefined)
55+
t.equal(await p2, undefined)
56+
57+
t.equal(c.get(1, { allowStale: true }), undefined, 'get expect undef 1')
58+
t.equal(c.get(2, { allowStale: true }), undefined, 'get expect undef 2')
59+
60+
clock.advance(100)
61+
await new Promise<void>(res => queueMicrotask(res)).then(() => {})
62+
t.equal(c.get(1), 1, 'get expect 1')
63+
t.equal(c.get(2), undefined, 'get 2 expect undef')
64+
65+
clock.advance(100)
66+
await new Promise<void>(res => queueMicrotask(res)).then(() => {})
67+
t.equal(c.get(1), 1, 'get expect 1')
68+
t.equal(c.get(2), 2, 'get expect 2')

0 commit comments

Comments
 (0)