@naturalcycles/js-lib 15.98.0 → 15.98.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/decorators/asyncMemo.decorator.d.ts +16 -7
- package/dist/decorators/asyncMemo.decorator.js +12 -6
- package/dist/decorators/memo.decorator.d.ts +5 -1
- package/dist/decorators/memo.decorator.js +4 -0
- package/dist/decorators/memo.util.d.ts +3 -4
- package/dist/decorators/memo.util.js +7 -12
- package/dist/decorators/memoizedAsync.d.ts +7 -1
- package/dist/decorators/memoizedAsync.js +11 -4
- package/package.json +1 -1
- package/src/decorators/asyncMemo.decorator.ts +22 -9
- package/src/decorators/memo.decorator.ts +5 -1
- package/src/decorators/memo.util.ts +7 -10
- package/src/decorators/memoizedAsync.ts +15 -4
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import type { CommonLogger } from '../log/commonLogger.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { AnyFunction, AnyObject, MaybeParameters } from '../types.js';
|
|
3
3
|
import type { MethodDecorator } from './decorator.util.js';
|
|
4
4
|
import type { AsyncMemoCache } from './memo.util.js';
|
|
5
5
|
export interface AsyncMemoOptions<FN> {
|
|
6
6
|
/**
|
|
7
7
|
* Provide a custom implementation of AsyncMemoCache.
|
|
8
8
|
* Function that creates an instance of `AsyncMemoCache`.
|
|
9
|
+
* Defaults to in-memory `MapAsyncMemoCache`, which never expires.
|
|
10
|
+
* e.g `LRUAsyncMemoCache` (ttl, max) from `@naturalcycles/nodejs-lib`,
|
|
11
|
+
* or `CommonKeyValueDaoMemoCache` (persisted in a DB) from `@naturalcycles/db-lib`.
|
|
9
12
|
*/
|
|
10
|
-
cacheFactory
|
|
13
|
+
cacheFactory?: () => AsyncMemoCache;
|
|
11
14
|
/**
|
|
12
15
|
* Provide a custom implementation of CacheKey function.
|
|
13
16
|
*/
|
|
@@ -23,17 +26,23 @@ export interface AsyncMemoInstance {
|
|
|
23
26
|
*/
|
|
24
27
|
clear: () => Promise<void>;
|
|
25
28
|
getInstanceCache: () => Map<AnyObject, AsyncMemoCache>;
|
|
26
|
-
getCache: (instance:
|
|
29
|
+
getCache: (instance: AnyObject) => AsyncMemoCache | undefined;
|
|
27
30
|
}
|
|
28
31
|
/**
|
|
29
|
-
* Like `@_Memo`, but
|
|
32
|
+
* Like `@_Memo`, but awaits the method and caches the resolved **value**, not the Promise:
|
|
33
|
+
*
|
|
34
|
+
* - A rejection is NOT cached, so the next call retries the method.
|
|
35
|
+
* `@_Memo` caches the Promise as-is, so it serves a rejection to every caller until the key is evicted.
|
|
36
|
+
* - The cache is async (`AsyncMemoCache`), so it can be backed by e.g Redis or Datastore
|
|
37
|
+
* and survive a process restart.
|
|
38
|
+
* - Parallel calls with the same key share one in-flight Promise, also while the cache lookup is pending.
|
|
39
|
+
*
|
|
40
|
+
* Cache errors (`get`/`set`) are logged and treated as a cache miss, they never fail the method.
|
|
30
41
|
*
|
|
31
42
|
* Implementation is more complex than `@_Memo`, because it needs to handle "in-flight" Promises
|
|
32
43
|
* while waiting for cache to resolve, to prevent "async swarm" issue.
|
|
33
|
-
*
|
|
34
|
-
* @experimental consider normal `@_Memo` for most of the cases, it's stable and predictable
|
|
35
44
|
*/
|
|
36
|
-
export declare const _AsyncMemo: <FN>(opt
|
|
45
|
+
export declare const _AsyncMemo: <FN>(opt?: AsyncMemoOptions<FN>) => MethodDecorator<FN>;
|
|
37
46
|
/**
|
|
38
47
|
Call it on a method that is decorated with `@_AsyncMemo` to get access to additional functions,
|
|
39
48
|
e.g `clear` to clear the cache, or get its underlying data.
|
|
@@ -2,16 +2,22 @@ import { _assert, _assertTypeOf } from '../error/assert.js';
|
|
|
2
2
|
import { consoleLogger } from '../log/commonLogger.js';
|
|
3
3
|
import { _objectAssign, MISS } from '../types.js';
|
|
4
4
|
import { _getTargetMethodSignature } from './decorator.util.js';
|
|
5
|
-
import { jsonMemoSerializer } from './memo.util.js';
|
|
5
|
+
import { jsonMemoSerializer, MapAsyncMemoCache } from './memo.util.js';
|
|
6
6
|
/**
|
|
7
|
-
* Like `@_Memo`, but
|
|
7
|
+
* Like `@_Memo`, but awaits the method and caches the resolved **value**, not the Promise:
|
|
8
|
+
*
|
|
9
|
+
* - A rejection is NOT cached, so the next call retries the method.
|
|
10
|
+
* `@_Memo` caches the Promise as-is, so it serves a rejection to every caller until the key is evicted.
|
|
11
|
+
* - The cache is async (`AsyncMemoCache`), so it can be backed by e.g Redis or Datastore
|
|
12
|
+
* and survive a process restart.
|
|
13
|
+
* - Parallel calls with the same key share one in-flight Promise, also while the cache lookup is pending.
|
|
14
|
+
*
|
|
15
|
+
* Cache errors (`get`/`set`) are logged and treated as a cache miss, they never fail the method.
|
|
8
16
|
*
|
|
9
17
|
* Implementation is more complex than `@_Memo`, because it needs to handle "in-flight" Promises
|
|
10
18
|
* while waiting for cache to resolve, to prevent "async swarm" issue.
|
|
11
|
-
*
|
|
12
|
-
* @experimental consider normal `@_Memo` for most of the cases, it's stable and predictable
|
|
13
19
|
*/
|
|
14
|
-
export const _AsyncMemo = (opt) => (target, key, descriptor) => {
|
|
20
|
+
export const _AsyncMemo = (opt = {}) => (target, key, descriptor) => {
|
|
15
21
|
_assertTypeOf(descriptor.value, 'function', 'Memoization can be applied only to methods');
|
|
16
22
|
const originalFn = descriptor.value;
|
|
17
23
|
// Map from "instance" of the Class where @_AsyncMemo is applied to AsyncMemoCache instance.
|
|
@@ -21,7 +27,7 @@ export const _AsyncMemo = (opt) => (target, key, descriptor) => {
|
|
|
21
27
|
// It only holds values that are "in-flight", until Promise is resolved
|
|
22
28
|
// After it's resolved - it's evicted from the cache and moved to the "proper" `instanceCache`
|
|
23
29
|
const instancePromiseCache = new Map();
|
|
24
|
-
const { logger = consoleLogger, cacheFactory, cacheKeyFn = jsonMemoSerializer } = opt;
|
|
30
|
+
const { logger = consoleLogger, cacheFactory = () => new MapAsyncMemoCache(), cacheKeyFn = jsonMemoSerializer, } = opt;
|
|
25
31
|
const keyStr = String(key);
|
|
26
32
|
const methodSignature = _getTargetMethodSignature(target, keyStr);
|
|
27
33
|
// oxlint-disable-next-line @typescript-eslint/promise-function-async
|
|
@@ -24,7 +24,7 @@ export interface MemoInstance {
|
|
|
24
24
|
*/
|
|
25
25
|
clear: () => void;
|
|
26
26
|
getInstanceCache: () => Map<AnyObject, MemoCache>;
|
|
27
|
-
getCache: (instance:
|
|
27
|
+
getCache: (instance: AnyObject) => MemoCache | undefined;
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
30
|
* Memoizes the method of the class, so it caches the output and returns the cached version if the "key"
|
|
@@ -35,6 +35,10 @@ export interface MemoInstance {
|
|
|
35
35
|
* So, error-throwing functions will be called multiple times.
|
|
36
36
|
* Therefor, if the origin function can possibly throw - it should try to be idempotent.
|
|
37
37
|
*
|
|
38
|
+
* For an async method it's the returned **Promise** that is cached, so a rejected Promise IS cached:
|
|
39
|
+
* every caller gets the same rejection until the key is evicted from the cache.
|
|
40
|
+
* Use `@_AsyncMemo` to cache only the resolved value and retry the method on rejection.
|
|
41
|
+
*
|
|
38
42
|
* Cache is stored **per instance** - separate cache for separate instances of the class.
|
|
39
43
|
* If you don't want it that way - you can use a static method, then there will be only one "instance".
|
|
40
44
|
*
|
|
@@ -12,6 +12,10 @@ import { jsonMemoSerializer, MapMemoCache } from './memo.util.js';
|
|
|
12
12
|
* So, error-throwing functions will be called multiple times.
|
|
13
13
|
* Therefor, if the origin function can possibly throw - it should try to be idempotent.
|
|
14
14
|
*
|
|
15
|
+
* For an async method it's the returned **Promise** that is cached, so a rejected Promise IS cached:
|
|
16
|
+
* every caller gets the same rejection until the key is evicted from the cache.
|
|
17
|
+
* Use `@_AsyncMemo` to cache only the resolved value and retry the method on rejection.
|
|
18
|
+
*
|
|
15
19
|
* Cache is stored **per instance** - separate cache for separate instances of the class.
|
|
16
20
|
* If you don't want it that way - you can use a static method, then there will be only one "instance".
|
|
17
21
|
*
|
|
@@ -48,12 +48,11 @@ export declare class MapMemoCache<KEY = any, VALUE = any> implements MemoCache<K
|
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
50
|
* Implementation of AsyncMemoCache backed by a synchronous Map.
|
|
51
|
-
*
|
|
52
|
-
*
|
|
51
|
+
* The default cache of `@_AsyncMemo`: in-memory, never expires.
|
|
52
|
+
* Use it over `@_Memo` when a rejection should not be cached (e.g an `init()` that may fail and
|
|
53
|
+
* should be retried). For an **async** backed cache (Redis, Datastore) use another implementation.
|
|
53
54
|
*/
|
|
54
55
|
export declare class MapAsyncMemoCache<KEY = any, VALUE = any> implements AsyncMemoCache<KEY, VALUE> {
|
|
55
|
-
private delay;
|
|
56
|
-
constructor(delay?: number);
|
|
57
56
|
private m;
|
|
58
57
|
get(k: KEY): Promise<VALUE | typeof MISS>;
|
|
59
58
|
set(k: KEY, v: VALUE): Promise<void>;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { _isPrimitive } from '../is.util.js';
|
|
2
|
-
import { pDelay } from '../promise/pDelay.js';
|
|
3
2
|
import { MISS } from '../types.js';
|
|
4
3
|
export const jsonMemoSerializer = args => {
|
|
5
4
|
if (args.length === 0)
|
|
@@ -70,27 +69,23 @@ export class MapMemoCache {
|
|
|
70
69
|
}
|
|
71
70
|
/**
|
|
72
71
|
* Implementation of AsyncMemoCache backed by a synchronous Map.
|
|
73
|
-
*
|
|
74
|
-
*
|
|
72
|
+
* The default cache of `@_AsyncMemo`: in-memory, never expires.
|
|
73
|
+
* Use it over `@_Memo` when a rejection should not be cached (e.g an `init()` that may fail and
|
|
74
|
+
* should be retried). For an **async** backed cache (Redis, Datastore) use another implementation.
|
|
75
75
|
*/
|
|
76
76
|
export class MapAsyncMemoCache {
|
|
77
|
-
delay;
|
|
78
|
-
constructor(delay = 0) {
|
|
79
|
-
this.delay = delay;
|
|
80
|
-
}
|
|
81
77
|
m = new Map();
|
|
82
78
|
async get(k) {
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
const v = this.m.get(k);
|
|
80
|
+
// `undefined` is a valid VALUE, only then `has` tells it apart from a miss
|
|
81
|
+
if (v === undefined && !this.m.has(k))
|
|
85
82
|
return MISS;
|
|
86
|
-
return
|
|
83
|
+
return v;
|
|
87
84
|
}
|
|
88
85
|
async set(k, v) {
|
|
89
|
-
await pDelay(this.delay);
|
|
90
86
|
this.m.set(k, v);
|
|
91
87
|
}
|
|
92
88
|
async clear() {
|
|
93
|
-
await pDelay(this.delay);
|
|
94
89
|
this.m.clear();
|
|
95
90
|
}
|
|
96
91
|
}
|
|
@@ -4,4 +4,10 @@ import type { AsyncMemoCache } from './memo.util.js';
|
|
|
4
4
|
export interface MemoizedAsyncFunction {
|
|
5
5
|
cache: AsyncMemoCache;
|
|
6
6
|
}
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Function version of `@_AsyncMemo`: awaits `fn` and caches the resolved value, not the Promise,
|
|
9
|
+
* so a rejection is not cached. Cache errors are logged and treated as a miss.
|
|
10
|
+
*
|
|
11
|
+
* Unlike the decorator, it does NOT dedupe parallel calls with the same key.
|
|
12
|
+
*/
|
|
13
|
+
export declare function _memoizedAsync<FN extends AnyAsyncFunction>(fn: FN, opt?: AsyncMemoOptions<FN>): FN & MemoizedAsyncFunction;
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { consoleLogger } from '../log/commonLogger.js';
|
|
2
2
|
import { MISS } from '../types.js';
|
|
3
|
-
import { jsonMemoSerializer } from './memo.util.js';
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import { jsonMemoSerializer, MapAsyncMemoCache } from './memo.util.js';
|
|
4
|
+
/**
|
|
5
|
+
* Function version of `@_AsyncMemo`: awaits `fn` and caches the resolved value, not the Promise,
|
|
6
|
+
* so a rejection is not cached. Cache errors are logged and treated as a miss.
|
|
7
|
+
*
|
|
8
|
+
* Unlike the decorator, it does NOT dedupe parallel calls with the same key.
|
|
9
|
+
*/
|
|
10
|
+
export function _memoizedAsync(fn, opt = {}) {
|
|
11
|
+
const { logger = consoleLogger, cacheFactory = () => new MapAsyncMemoCache(), cacheKeyFn = jsonMemoSerializer, } = opt;
|
|
6
12
|
const cache = cacheFactory();
|
|
7
13
|
const memoizedFn = async function (...args) {
|
|
8
14
|
const ctx = this;
|
|
9
15
|
const cacheKey = cacheKeyFn(args);
|
|
10
|
-
|
|
16
|
+
// MISS, so that a failed cache `get` is a miss too (and not a cached `undefined`)
|
|
17
|
+
let value = MISS;
|
|
11
18
|
try {
|
|
12
19
|
value = await cache.get(cacheKey);
|
|
13
20
|
}
|
package/package.json
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { _assert, _assertTypeOf } from '../error/assert.js'
|
|
2
2
|
import { consoleLogger } from '../log/commonLogger.js'
|
|
3
3
|
import type { CommonLogger } from '../log/commonLogger.js'
|
|
4
|
-
import type {
|
|
4
|
+
import type { AnyFunction, AnyObject, MaybeParameters } from '../types.js'
|
|
5
5
|
import { _objectAssign, MISS } from '../types.js'
|
|
6
6
|
import type { MethodDecorator } from './decorator.util.js'
|
|
7
7
|
import { _getTargetMethodSignature } from './decorator.util.js'
|
|
8
8
|
import type { AsyncMemoCache } from './memo.util.js'
|
|
9
|
-
import { jsonMemoSerializer } from './memo.util.js'
|
|
9
|
+
import { jsonMemoSerializer, MapAsyncMemoCache } from './memo.util.js'
|
|
10
10
|
|
|
11
11
|
export interface AsyncMemoOptions<FN> {
|
|
12
12
|
/**
|
|
13
13
|
* Provide a custom implementation of AsyncMemoCache.
|
|
14
14
|
* Function that creates an instance of `AsyncMemoCache`.
|
|
15
|
+
* Defaults to in-memory `MapAsyncMemoCache`, which never expires.
|
|
16
|
+
* e.g `LRUAsyncMemoCache` (ttl, max) from `@naturalcycles/nodejs-lib`,
|
|
17
|
+
* or `CommonKeyValueDaoMemoCache` (persisted in a DB) from `@naturalcycles/db-lib`.
|
|
15
18
|
*/
|
|
16
|
-
cacheFactory
|
|
19
|
+
cacheFactory?: () => AsyncMemoCache
|
|
17
20
|
|
|
18
21
|
/**
|
|
19
22
|
* Provide a custom implementation of CacheKey function.
|
|
@@ -34,19 +37,25 @@ export interface AsyncMemoInstance {
|
|
|
34
37
|
|
|
35
38
|
getInstanceCache: () => Map<AnyObject, AsyncMemoCache>
|
|
36
39
|
|
|
37
|
-
getCache: (instance:
|
|
40
|
+
getCache: (instance: AnyObject) => AsyncMemoCache | undefined
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
/**
|
|
41
|
-
* Like `@_Memo`, but
|
|
44
|
+
* Like `@_Memo`, but awaits the method and caches the resolved **value**, not the Promise:
|
|
45
|
+
*
|
|
46
|
+
* - A rejection is NOT cached, so the next call retries the method.
|
|
47
|
+
* `@_Memo` caches the Promise as-is, so it serves a rejection to every caller until the key is evicted.
|
|
48
|
+
* - The cache is async (`AsyncMemoCache`), so it can be backed by e.g Redis or Datastore
|
|
49
|
+
* and survive a process restart.
|
|
50
|
+
* - Parallel calls with the same key share one in-flight Promise, also while the cache lookup is pending.
|
|
51
|
+
*
|
|
52
|
+
* Cache errors (`get`/`set`) are logged and treated as a cache miss, they never fail the method.
|
|
42
53
|
*
|
|
43
54
|
* Implementation is more complex than `@_Memo`, because it needs to handle "in-flight" Promises
|
|
44
55
|
* while waiting for cache to resolve, to prevent "async swarm" issue.
|
|
45
|
-
*
|
|
46
|
-
* @experimental consider normal `@_Memo` for most of the cases, it's stable and predictable
|
|
47
56
|
*/
|
|
48
57
|
export const _AsyncMemo =
|
|
49
|
-
<FN>(opt: AsyncMemoOptions<FN>): MethodDecorator<FN> =>
|
|
58
|
+
<FN>(opt: AsyncMemoOptions<FN> = {}): MethodDecorator<FN> =>
|
|
50
59
|
(target, key, descriptor) => {
|
|
51
60
|
_assertTypeOf<AnyFunction>(
|
|
52
61
|
descriptor.value,
|
|
@@ -65,7 +74,11 @@ export const _AsyncMemo =
|
|
|
65
74
|
// After it's resolved - it's evicted from the cache and moved to the "proper" `instanceCache`
|
|
66
75
|
const instancePromiseCache = new Map<AnyObject, Map<any, Promise<any>>>()
|
|
67
76
|
|
|
68
|
-
const {
|
|
77
|
+
const {
|
|
78
|
+
logger = consoleLogger,
|
|
79
|
+
cacheFactory = () => new MapAsyncMemoCache(),
|
|
80
|
+
cacheKeyFn = jsonMemoSerializer,
|
|
81
|
+
} = opt
|
|
69
82
|
|
|
70
83
|
const keyStr = String(key)
|
|
71
84
|
const methodSignature = _getTargetMethodSignature(target, keyStr)
|
|
@@ -35,7 +35,7 @@ export interface MemoInstance {
|
|
|
35
35
|
|
|
36
36
|
getInstanceCache: () => Map<AnyObject, MemoCache>
|
|
37
37
|
|
|
38
|
-
getCache: (instance:
|
|
38
|
+
getCache: (instance: AnyObject) => MemoCache | undefined
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
@@ -47,6 +47,10 @@ export interface MemoInstance {
|
|
|
47
47
|
* So, error-throwing functions will be called multiple times.
|
|
48
48
|
* Therefor, if the origin function can possibly throw - it should try to be idempotent.
|
|
49
49
|
*
|
|
50
|
+
* For an async method it's the returned **Promise** that is cached, so a rejected Promise IS cached:
|
|
51
|
+
* every caller gets the same rejection until the key is evicted from the cache.
|
|
52
|
+
* Use `@_AsyncMemo` to cache only the resolved value and retry the method on rejection.
|
|
53
|
+
*
|
|
50
54
|
* Cache is stored **per instance** - separate cache for separate instances of the class.
|
|
51
55
|
* If you don't want it that way - you can use a static method, then there will be only one "instance".
|
|
52
56
|
*
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { _isPrimitive } from '../is.util.js'
|
|
2
|
-
import { pDelay } from '../promise/pDelay.js'
|
|
3
2
|
import type { UnixTimestamp } from '../types.js'
|
|
4
3
|
import { MISS } from '../types.js'
|
|
5
4
|
|
|
@@ -123,27 +122,25 @@ export class MapMemoCache<KEY = any, VALUE = any> implements MemoCache<KEY, VALU
|
|
|
123
122
|
|
|
124
123
|
/**
|
|
125
124
|
* Implementation of AsyncMemoCache backed by a synchronous Map.
|
|
126
|
-
*
|
|
127
|
-
*
|
|
125
|
+
* The default cache of `@_AsyncMemo`: in-memory, never expires.
|
|
126
|
+
* Use it over `@_Memo` when a rejection should not be cached (e.g an `init()` that may fail and
|
|
127
|
+
* should be retried). For an **async** backed cache (Redis, Datastore) use another implementation.
|
|
128
128
|
*/
|
|
129
129
|
export class MapAsyncMemoCache<KEY = any, VALUE = any> implements AsyncMemoCache<KEY, VALUE> {
|
|
130
|
-
constructor(private delay = 0) {}
|
|
131
|
-
|
|
132
130
|
private m = new Map<KEY, VALUE>()
|
|
133
131
|
|
|
134
132
|
async get(k: KEY): Promise<VALUE | typeof MISS> {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
133
|
+
const v = this.m.get(k)
|
|
134
|
+
// `undefined` is a valid VALUE, only then `has` tells it apart from a miss
|
|
135
|
+
if (v === undefined && !this.m.has(k)) return MISS
|
|
136
|
+
return v!
|
|
138
137
|
}
|
|
139
138
|
|
|
140
139
|
async set(k: KEY, v: VALUE): Promise<void> {
|
|
141
|
-
await pDelay(this.delay)
|
|
142
140
|
this.m.set(k, v)
|
|
143
141
|
}
|
|
144
142
|
|
|
145
143
|
async clear(): Promise<void> {
|
|
146
|
-
await pDelay(this.delay)
|
|
147
144
|
this.m.clear()
|
|
148
145
|
}
|
|
149
146
|
}
|
|
@@ -3,24 +3,35 @@ import type { AnyAsyncFunction, MaybeParameters } from '../types.js'
|
|
|
3
3
|
import { MISS } from '../types.js'
|
|
4
4
|
import type { AsyncMemoOptions } from './asyncMemo.decorator.js'
|
|
5
5
|
import type { AsyncMemoCache } from './memo.util.js'
|
|
6
|
-
import { jsonMemoSerializer } from './memo.util.js'
|
|
6
|
+
import { jsonMemoSerializer, MapAsyncMemoCache } from './memo.util.js'
|
|
7
7
|
|
|
8
8
|
export interface MemoizedAsyncFunction {
|
|
9
9
|
cache: AsyncMemoCache
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Function version of `@_AsyncMemo`: awaits `fn` and caches the resolved value, not the Promise,
|
|
14
|
+
* so a rejection is not cached. Cache errors are logged and treated as a miss.
|
|
15
|
+
*
|
|
16
|
+
* Unlike the decorator, it does NOT dedupe parallel calls with the same key.
|
|
17
|
+
*/
|
|
12
18
|
export function _memoizedAsync<FN extends AnyAsyncFunction>(
|
|
13
19
|
fn: FN,
|
|
14
|
-
opt: AsyncMemoOptions<FN
|
|
20
|
+
opt: AsyncMemoOptions<FN> = {},
|
|
15
21
|
): FN & MemoizedAsyncFunction {
|
|
16
|
-
const {
|
|
22
|
+
const {
|
|
23
|
+
logger = consoleLogger,
|
|
24
|
+
cacheFactory = () => new MapAsyncMemoCache(),
|
|
25
|
+
cacheKeyFn = jsonMemoSerializer,
|
|
26
|
+
} = opt
|
|
17
27
|
|
|
18
28
|
const cache = cacheFactory()
|
|
19
29
|
|
|
20
30
|
const memoizedFn = async function (this: any, ...args: MaybeParameters<FN>): Promise<any> {
|
|
21
31
|
const ctx = this
|
|
22
32
|
const cacheKey = cacheKeyFn(args)
|
|
23
|
-
|
|
33
|
+
// MISS, so that a failed cache `get` is a miss too (and not a cached `undefined`)
|
|
34
|
+
let value: any = MISS
|
|
24
35
|
|
|
25
36
|
try {
|
|
26
37
|
value = await cache.get(cacheKey)
|