Skip to content

Commit d371ffa

Browse files
authored
Merge pull request #784 from devoxa/fix/memoize-on-errors
2 parents 8668669 + 62b9f28 commit d371ffa

File tree

4 files changed

+65
-9
lines changed

4 files changed

+65
-9
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ memoizedFunc(1, 2)
260260
with some improvements for variadic performance and additional support for a TTL based cache.
261261
</details>
262262

263-
<sup>[Source](./src/memoize/memoize.ts)[Benchmark](./src/memoize/BENCHMARK.md) • Minify: 820 B • Minify & GZIP: 393 B<sup>
263+
<sup>[Source](./src/memoize/memoize.ts)[Benchmark](./src/memoize/BENCHMARK.md) • Minify: 962 B • Minify & GZIP: 441 B<sup>
264264

265265
### min(array)
266266

src/memoize/BENCHMARK.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
[Source for this benchmark](./benchmark.ts)
44

5-
| | lodash | fast-memoize | flocky |
6-
| -------------------- | --------------------------- | ---------------------------- | --------------------------------- |
7-
| monadic (primitive) | 70,899,764 ops/sec (34.95%) | 201,953,383 ops/sec (99.54%) | **202,886,184 ops/sec (100.00%)** |
8-
| monadic (serialized) | 2,826,332 ops/sec (37.76%) | 2,272,833 ops/sec (30.36%) | **7,485,491 ops/sec (100.00%)** |
9-
| variadic | 2,893,648 ops/sec (71.46%) | 1,414,302 ops/sec (34.93%) | **4,049,054 ops/sec (100.00%)** |
5+
| | lodash | fast-memoize | flocky |
6+
| -------------------- | --------------------------- | --------------------------------- | ------------------------------- |
7+
| monadic (primitive) | 70,207,892 ops/sec (34.59%) | **202,998,947 ops/sec (100.00%)** | 199,242,881 ops/sec (98.15%) |
8+
| monadic (serialized) | 2,884,723 ops/sec (40.50%) | 2,409,543 ops/sec (33.83%) | **7,122,748 ops/sec (100.00%)** |
9+
| variadic | 2,864,947 ops/sec (76.45%) | 1,486,428 ops/sec (39.67%) | **3,747,245 ops/sec (100.00%)** |
1010

11-
<sup>Generated at 2021-07-05 with Node.JS v16.4.1</sup>
11+
<sup>Generated at 2023-11-27 with Node.JS v18.18.2</sup>

src/memoize/memoize.spec.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { sleep } from '../sleep/sleep'
2-
import { memoize } from './memoize'
2+
import { MemoizeOptions, memoize } from './memoize'
33

44
type NumberObject = { n: number }
55

@@ -223,6 +223,10 @@ describe('memoize', () => {
223223
expect(await a).toEqual('A')
224224
expect(await b).toEqual('A')
225225
expect(calls).toEqual(1)
226+
227+
const c = memoizedFunc()
228+
expect(await c).toEqual('A')
229+
expect(calls).toEqual(1)
226230
})
227231

228232
test('memoizes function calls with a maximum TTL', async () => {
@@ -254,6 +258,45 @@ describe('memoize', () => {
254258
])
255259
})
256260

261+
test.each([
262+
['monadic', { strategy: 'monadic' }],
263+
['variadic', { strategy: 'variadic' }],
264+
['ttl', { ttl: 50 }],
265+
] as Array<[string, MemoizeOptions]>)(
266+
'smartly memoizes function calls that return promise rejections (%s)',
267+
async (_: string, options: MemoizeOptions) => {
268+
let calls = 0
269+
const func = async () => {
270+
calls++
271+
await sleep(10)
272+
273+
if (calls === 1) {
274+
throw new Error('Uh oh')
275+
}
276+
277+
return 'A'
278+
}
279+
280+
const memoizedFunc = memoize(func, options)
281+
282+
// We want to memoize the second call because the Promise is still pending
283+
const a = memoizedFunc()
284+
expect(a).toBeInstanceOf(Promise)
285+
const b = memoizedFunc()
286+
expect(b).toBeInstanceOf(Promise)
287+
expect(calls).toEqual(1)
288+
289+
await expect(a).rejects.toThrow('Uh oh')
290+
await expect(b).rejects.toThrow('Uh oh')
291+
expect(calls).toEqual(1)
292+
293+
// We don't want to memoize the third call because the Promise has rejected
294+
const c = memoizedFunc()
295+
expect(await c).toEqual('A')
296+
expect(calls).toEqual(2)
297+
}
298+
)
299+
257300
test('has the correct type', async () => {
258301
const func = (a: number, b: number): number => {
259302
return a + b

src/memoize/memoize.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { TAnyFunction } from '../typeHelpers'
22

3-
interface MemoizeOptions {
3+
export interface MemoizeOptions {
44
strategy?: MemoizeStrategy
55
serializer?: MemoizeSerializer
66
ttl?: number
@@ -63,6 +63,11 @@ function monadic<TThis, TReturn, TFunc extends TAnyFunction<TReturn>>(
6363
let value = cache.get(cacheKey)
6464
if (typeof value === 'undefined') {
6565
value = func.call(this, arg)
66+
67+
if (value instanceof Promise) {
68+
value.catch(() => cache.remove(cacheKey))
69+
}
70+
6671
cache.set(cacheKey, value)
6772
}
6873

@@ -81,6 +86,11 @@ function variadic<TThis, TReturn, TFunc extends TAnyFunction<TReturn>>(
8186
let value = cache.get(cacheKey)
8287
if (typeof value === 'undefined') {
8388
value = func.apply(this, args)
89+
90+
if (value instanceof Promise) {
91+
value.catch(() => cache.remove(cacheKey))
92+
}
93+
8494
cache.set(cacheKey, value)
8595
}
8696

@@ -94,6 +104,7 @@ function defaultSerializer(data: unknown) {
94104
interface MemoizeCache<TReturn> {
95105
get: (key: string) => TReturn | undefined
96106
set: (key: string, value: TReturn) => void
107+
remove: (key: string) => void
97108
}
98109

99110
function defaultCache<TReturn>(): MemoizeCache<TReturn> {
@@ -104,6 +115,7 @@ function defaultCache<TReturn>(): MemoizeCache<TReturn> {
104115
set: (key, value) => {
105116
cache[key] = value
106117
},
118+
remove: (key) => delete cache[key],
107119
}
108120
}
109121

@@ -121,5 +133,6 @@ function ttlCache<TReturn>(ttl: number): MemoizeCache<TReturn> {
121133
delete cache[key]
122134
}, ttl)
123135
},
136+
remove: (key) => delete cache[key],
124137
}
125138
}

0 commit comments

Comments
 (0)