@naturalcycles/js-lib 14.215.0 → 14.216.0

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.
@@ -1,38 +1,40 @@
1
1
  import type { CommonLogger } from '../log/commonLogger';
2
- import type { AsyncMemoCache } from './memo.util';
2
+ import { AnyAsyncFunction, AnyFunction, AnyObject } from '../types';
3
+ import { AsyncMemoCache } from './memo.util';
3
4
  export interface AsyncMemoOptions {
4
5
  /**
5
- * Provide a custom implementation of MemoCache.
6
- * Function that creates an instance of `MemoCache`.
7
- * e.g LRUMemoCache from `@naturalcycles/nodejs-lib`.
6
+ * Provide a custom implementation of AsyncMemoCache.
7
+ * Function that creates an instance of `AsyncMemoCache`.
8
8
  */
9
- cacheFactory?: () => AsyncMemoCache;
9
+ cacheFactory: () => AsyncMemoCache;
10
10
  /**
11
11
  * Provide a custom implementation of CacheKey function.
12
12
  */
13
13
  cacheKeyFn?: (args: any[]) => any;
14
- /**
15
- * Default true.
16
- *
17
- * Set to `false` to skip caching rejected promises (errors).
18
- *
19
- * True will ensure "max 1 execution", but will "remember" rejection.
20
- * False will allow >1 execution in case of errors.
21
- */
22
- cacheRejections?: boolean;
23
14
  /**
24
15
  * Default to `console`
25
16
  */
26
17
  logger?: CommonLogger;
27
18
  }
19
+ export interface AsyncMemoInstance {
20
+ /**
21
+ * Clears the cache.
22
+ */
23
+ clear: () => Promise<void>;
24
+ getInstanceCache: () => Map<AnyObject, AsyncMemoCache>;
25
+ getCache: (instance: AnyAsyncFunction) => AsyncMemoCache | undefined;
26
+ }
28
27
  /**
29
28
  * Like @_Memo, but allowing async MemoCache implementation.
30
29
  *
31
- * Important: it awaits the method to return the result before caching it.
32
- *
33
- * todo: test for "swarm requests", it should return "the same promise" and not cause a swarm origin hit
30
+ * Implementation is more complex than @_Memo, because it needs to handle "in-flight" Promises
31
+ * while waiting for cache to resolve, to prevent "async swarm" issue.
34
32
  *
35
- * Method CANNOT return `undefined`, as undefined will always be treated as cache MISS and retried.
36
- * Return `null` instead (it'll be cached).
33
+ * @experimental consider normal @_Memo for most of the cases, it's stable and predictable
34
+ */
35
+ export declare const _AsyncMemo: (opt: AsyncMemoOptions) => MethodDecorator;
36
+ /**
37
+ Call it on a method that is decorated with `@_AsyncMemo` to get access to additional functions,
38
+ e.g `clear` to clear the cache, or get its underlying data.
37
39
  */
38
- export declare const _AsyncMemo: (opt?: AsyncMemoOptions) => MethodDecorator;
40
+ export declare function _getAsyncMemo(method: AnyFunction): AsyncMemoInstance;
@@ -1,95 +1,118 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports._AsyncMemo = void 0;
3
+ exports._getAsyncMemo = exports._AsyncMemo = void 0;
4
+ const assert_1 = require("../error/assert");
5
+ const types_1 = require("../types");
4
6
  const decorator_util_1 = require("./decorator.util");
5
7
  const memo_util_1 = require("./memo.util");
6
8
  /**
7
9
  * Like @_Memo, but allowing async MemoCache implementation.
8
10
  *
9
- * Important: it awaits the method to return the result before caching it.
11
+ * Implementation is more complex than @_Memo, because it needs to handle "in-flight" Promises
12
+ * while waiting for cache to resolve, to prevent "async swarm" issue.
10
13
  *
11
- * todo: test for "swarm requests", it should return "the same promise" and not cause a swarm origin hit
12
- *
13
- * Method CANNOT return `undefined`, as undefined will always be treated as cache MISS and retried.
14
- * Return `null` instead (it'll be cached).
14
+ * @experimental consider normal @_Memo for most of the cases, it's stable and predictable
15
15
  */
16
16
  // eslint-disable-next-line @typescript-eslint/naming-convention
17
- const _AsyncMemo = (opt = {}) => (target, key, descriptor) => {
17
+ const _AsyncMemo = (opt) => (target, key, descriptor) => {
18
18
  if (typeof descriptor.value !== 'function') {
19
19
  throw new TypeError('Memoization can be applied only to methods');
20
20
  }
21
21
  const originalFn = descriptor.value;
22
22
  // Map from "instance" of the Class where @_AsyncMemo is applied to AsyncMemoCache instance.
23
- const cache = new Map();
24
- const { logger = console, cacheFactory = () => new memo_util_1.MapMemoCache(), cacheKeyFn = memo_util_1.jsonMemoSerializer, cacheRejections = true, } = opt;
23
+ const instanceCache = new Map();
24
+ // Cache from Instance to Map<key, Promise>
25
+ // This cache is temporary, with only one purpose - to prevent "async swarm"
26
+ // It only holds values that are "in-flight", until Promise is resolved
27
+ // After it's resolved - it's evicted from the cache and moved to the "proper" `instanceCache`
28
+ const instancePromiseCache = new Map();
29
+ const { logger = console, cacheFactory, cacheKeyFn = memo_util_1.jsonMemoSerializer } = opt;
25
30
  const keyStr = String(key);
26
31
  const methodSignature = (0, decorator_util_1._getTargetMethodSignature)(target, keyStr);
27
- descriptor.value = async function (...args) {
32
+ // eslint-disable-next-line @typescript-eslint/promise-function-async
33
+ descriptor.value = function (...args) {
28
34
  const ctx = this;
29
35
  const cacheKey = cacheKeyFn(args);
30
- if (!cache.has(ctx)) {
31
- cache.set(ctx, cacheFactory());
36
+ let cache = instanceCache.get(ctx);
37
+ let promiseCache = instancePromiseCache.get(ctx);
38
+ if (!cache) {
39
+ cache = cacheFactory();
40
+ instanceCache.set(ctx, cache);
32
41
  // here, no need to check the cache. It's definitely a miss, because the cacheLayers is just created
33
42
  // UPD: no! AsyncMemo supports "persistent caches" (e.g Database-backed cache)
34
43
  }
35
- let value;
36
- try {
37
- value = await cache.get(ctx).get(cacheKey);
44
+ if (!promiseCache) {
45
+ promiseCache = new Map();
46
+ instancePromiseCache.set(ctx, promiseCache);
38
47
  }
39
- catch (err) {
40
- // log error, but don't throw, treat it as a "miss"
41
- logger.error(err);
48
+ let promise = promiseCache.get(cacheKey);
49
+ // If there's already "in-flight" cache request - return that, to avoid "async swarm"
50
+ if (promise) {
51
+ // console.log('return promise', promiseCache.size)
52
+ return promise;
42
53
  }
43
- if (value !== undefined) {
44
- // hit!
45
- if (value instanceof Error) {
46
- throw value;
54
+ promise = cache.get(cacheKey).then(async (value) => {
55
+ if (value !== types_1.MISS) {
56
+ // console.log('hit', promiseCache.size)
57
+ promiseCache.delete(cacheKey);
58
+ return value;
47
59
  }
48
- return value;
49
- }
50
- // Here we know it's a MISS, let's execute the real method
51
- try {
52
- value = await originalFn.apply(ctx, args);
53
- // Save the value in the Cache, without awaiting it
54
- // This is to support both sync and async functions
55
- void (async () => {
56
- try {
57
- await cache.get(ctx).set(cacheKey, value);
58
- }
59
- catch (err) {
60
- // log and ignore the error
61
- logger.error(err);
62
- }
63
- })();
64
- return value;
65
- }
66
- catch (err) {
67
- if (cacheRejections) {
68
- // We put it to cache as raw Error, not Promise.reject(err)
69
- // This is to support both sync and async functions
60
+ // Miss
61
+ // console.log('miss', promiseCache.size)
62
+ return await onMiss();
63
+ }, async (err) => {
64
+ // Log the cache error and proceed "as cache Miss"
65
+ logger.error(err);
66
+ return await onMiss();
67
+ });
68
+ promiseCache.set(cacheKey, promise);
69
+ return promise;
70
+ //
71
+ async function onMiss() {
72
+ try {
73
+ const value = await originalFn.apply(ctx, args);
74
+ // Save the value in the Cache, in parallel,
75
+ // not to slow down the main function execution
76
+ // and not to fail on possible cache issues
70
77
  void (async () => {
71
78
  try {
72
- await cache.get(ctx).set(cacheKey, err);
79
+ await cache.set(cacheKey, value);
73
80
  }
74
81
  catch (err) {
75
- // log and ignore the error
76
- logger.error(err);
82
+ logger.error(err); // log and ignore the error
83
+ }
84
+ finally {
85
+ // Clear the "in-flight" promise cache entry, as we now have a "permanent" cache entry
86
+ promiseCache.delete(cacheKey);
87
+ // console.log('cache set and cleared', promiseCache!.size)
77
88
  }
78
89
  })();
90
+ return value;
91
+ }
92
+ catch (err) {
93
+ promiseCache.delete(cacheKey);
94
+ throw err;
79
95
  }
80
- throw err;
81
- }
82
- };
83
- descriptor.value.dropCache = async () => {
84
- logger.log(`${methodSignature} @_AsyncMemo.dropCache()`);
85
- try {
86
- await Promise.all([...cache.values()].map(c => c.clear()));
87
- cache.clear();
88
- }
89
- catch (err) {
90
- logger.error(err);
91
96
  }
92
97
  };
98
+ (0, types_1._objectAssign)(descriptor.value, {
99
+ clear: async () => {
100
+ logger.log(`${methodSignature} @_AsyncMemo.clear()`);
101
+ await Promise.all([...instanceCache.values()].map(c => c.clear()));
102
+ instanceCache.clear();
103
+ },
104
+ getInstanceCache: () => instanceCache,
105
+ getCache: instance => instanceCache.get(instance),
106
+ });
93
107
  return descriptor;
94
108
  };
95
109
  exports._AsyncMemo = _AsyncMemo;
110
+ /**
111
+ Call it on a method that is decorated with `@_AsyncMemo` to get access to additional functions,
112
+ e.g `clear` to clear the cache, or get its underlying data.
113
+ */
114
+ function _getAsyncMemo(method) {
115
+ (0, assert_1._assert)(typeof method?.getInstanceCache === 'function', 'method is not an AsyncMemo instance');
116
+ return method;
117
+ }
118
+ exports._getAsyncMemo = _getAsyncMemo;
@@ -1,4 +1,5 @@
1
1
  import type { CommonLogger } from '../log/commonLogger';
2
+ import { AnyFunction, AnyObject } from '../types';
2
3
  import type { MemoCache } from './memo.util';
3
4
  export interface MemoOptions {
4
5
  /**
@@ -11,31 +12,32 @@ export interface MemoOptions {
11
12
  * Provide a custom implementation of CacheKey function.
12
13
  */
13
14
  cacheKeyFn?: (args: any[]) => any;
14
- /**
15
- * Defaults to true.
16
- * Set to false to skip caching errors.
17
- *
18
- * True will ensure "max 1 execution", but will "remember" errors.
19
- * False will allow >1 execution in case of errors.
20
- */
21
- cacheErrors?: boolean;
22
15
  /**
23
16
  * Default to `console`
24
17
  */
25
18
  logger?: CommonLogger;
26
19
  }
20
+ export interface MemoInstance {
21
+ /**
22
+ * Clears the cache.
23
+ */
24
+ clear: () => void;
25
+ getInstanceCache: () => Map<AnyObject, MemoCache>;
26
+ getCache: (instance: AnyFunction) => MemoCache | undefined;
27
+ }
27
28
  /**
28
29
  * Memoizes the method of the class, so it caches the output and returns the cached version if the "key"
29
30
  * of the cache is the same. Key, by defaul, is calculated as `JSON.stringify(...args)`.
30
- * Cache is stored indefinitely in internal Map.
31
+ * Cache is stored indefinitely in the internal Map.
32
+ *
33
+ * If origin function throws an Error - it is NOT cached.
34
+ * So, error-throwing functions will be called multiple times.
35
+ * Therefor, if the origin function can possibly throw - it should try to be idempotent.
31
36
  *
32
37
  * Cache is stored **per instance** - separate cache for separate instances of the class.
33
38
  * If you don't want it that way - you can use a static method, then there will be only one "instance".
34
39
  *
35
- * Supports dropping it's cache by calling .dropCache() method of decorated function (useful in unit testing).
36
- *
37
- * Doesn't support Async functions, use @_AsyncMemo instead!
38
- * (or, it will simply return the [unresolved] Promise further, without awaiting it)
40
+ * Supports dropping it's cache by calling .clear() method of decorated function (useful in unit testing).
39
41
  *
40
42
  * Based on:
41
43
  * https://github.com/mgechev/memo-decorator/blob/master/index.ts
@@ -44,3 +46,8 @@ export interface MemoOptions {
44
46
  * https://community.risingstack.com/the-worlds-fastest-javascript-memoization-library/
45
47
  */
46
48
  export declare const _Memo: (opt?: MemoOptions) => MethodDecorator;
49
+ /**
50
+ Call it on a method that is decorated with `@_Memo` to get access to additional functions,
51
+ e.g `clear` to clear the cache, or get its underlying data.
52
+ */
53
+ export declare function _getMemo(method: AnyFunction): MemoInstance;
@@ -1,20 +1,23 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports._Memo = void 0;
3
+ exports._getMemo = exports._Memo = void 0;
4
+ const assert_1 = require("../error/assert");
5
+ const types_1 = require("../types");
4
6
  const decorator_util_1 = require("./decorator.util");
5
7
  const memo_util_1 = require("./memo.util");
6
8
  /**
7
9
  * Memoizes the method of the class, so it caches the output and returns the cached version if the "key"
8
10
  * of the cache is the same. Key, by defaul, is calculated as `JSON.stringify(...args)`.
9
- * Cache is stored indefinitely in internal Map.
11
+ * Cache is stored indefinitely in the internal Map.
12
+ *
13
+ * If origin function throws an Error - it is NOT cached.
14
+ * So, error-throwing functions will be called multiple times.
15
+ * Therefor, if the origin function can possibly throw - it should try to be idempotent.
10
16
  *
11
17
  * Cache is stored **per instance** - separate cache for separate instances of the class.
12
18
  * If you don't want it that way - you can use a static method, then there will be only one "instance".
13
19
  *
14
- * Supports dropping it's cache by calling .dropCache() method of decorated function (useful in unit testing).
15
- *
16
- * Doesn't support Async functions, use @_AsyncMemo instead!
17
- * (or, it will simply return the [unresolved] Promise further, without awaiting it)
20
+ * Supports dropping it's cache by calling .clear() method of decorated function (useful in unit testing).
18
21
  *
19
22
  * Based on:
20
23
  * https://github.com/mgechev/memo-decorator/blob/master/index.ts
@@ -31,55 +34,54 @@ const _Memo = (opt = {}) => (target, key, descriptor) => {
31
34
  // Map<ctx => MemoCache<cacheKey, result>>
32
35
  //
33
36
  // Internal map is from cacheKey to result
34
- // External map is from ctx (instance of class) to Internal map
37
+ // External map (instanceCache) is from ctx (instance of class) to Internal map
35
38
  // External map is Weak to not cause memory leaks, to allow ctx objects to be garbage collected
36
39
  // UPD: tests show that normal Map also doesn't leak (to be tested further)
37
- // Normal Map is needed to allow .dropCache()
38
- const cache = new Map();
39
- const { logger = console, cacheFactory = () => new memo_util_1.MapMemoCache(), cacheKeyFn = memo_util_1.jsonMemoSerializer, cacheErrors = true, } = opt;
40
+ // Normal Map is needed to allow .clear()
41
+ const instanceCache = new Map();
42
+ const { logger = console, cacheFactory = () => new memo_util_1.MapMemoCache(), cacheKeyFn = memo_util_1.jsonMemoSerializer, } = opt;
40
43
  const keyStr = String(key);
41
44
  const methodSignature = (0, decorator_util_1._getTargetMethodSignature)(target, keyStr);
42
45
  descriptor.value = function (...args) {
43
46
  const ctx = this;
44
47
  const cacheKey = cacheKeyFn(args);
45
- let value;
46
- if (!cache.has(ctx)) {
47
- cache.set(ctx, cacheFactory());
48
+ let cache = instanceCache.get(ctx);
49
+ if (!cache) {
50
+ cache = cacheFactory();
51
+ instanceCache.set(ctx, cache);
48
52
  }
49
- else if (cache.get(ctx).has(cacheKey)) {
50
- value = cache.get(ctx).get(cacheKey);
51
- if (value instanceof Error) {
52
- throw value;
53
- }
54
- return value;
53
+ if (cache.has(cacheKey)) {
54
+ // Hit
55
+ return cache.get(cacheKey);
55
56
  }
57
+ // Miss
58
+ const value = originalFn.apply(ctx, args);
56
59
  try {
57
- value = originalFn.apply(ctx, args);
58
- try {
59
- cache.get(ctx).set(cacheKey, value);
60
- }
61
- catch (err) {
62
- logger.error(err);
63
- }
64
- return value;
60
+ cache.set(cacheKey, value);
65
61
  }
66
62
  catch (err) {
67
- if (cacheErrors) {
68
- try {
69
- cache.get(ctx).set(cacheKey, err);
70
- }
71
- catch (err) {
72
- logger.error(err);
73
- }
74
- }
75
- throw err;
63
+ logger.error(err);
76
64
  }
65
+ return value;
77
66
  };
78
- descriptor.value.dropCache = () => {
79
- logger.log(`${methodSignature} @_Memo.dropCache()`);
80
- cache.forEach(memoCache => memoCache.clear());
81
- cache.clear();
82
- };
67
+ (0, types_1._objectAssign)(descriptor.value, {
68
+ clear: () => {
69
+ logger.log(`${methodSignature} @_Memo.clear()`);
70
+ instanceCache.forEach(memoCache => memoCache.clear());
71
+ instanceCache.clear();
72
+ },
73
+ getInstanceCache: () => instanceCache,
74
+ getCache: instance => instanceCache.get(instance),
75
+ });
83
76
  return descriptor;
84
77
  };
85
78
  exports._Memo = _Memo;
79
+ /**
80
+ Call it on a method that is decorated with `@_Memo` to get access to additional functions,
81
+ e.g `clear` to clear the cache, or get its underlying data.
82
+ */
83
+ function _getMemo(method) {
84
+ (0, assert_1._assert)(typeof method?.getInstanceCache === 'function', 'method is not a Memo instance');
85
+ return method;
86
+ }
87
+ exports._getMemo = _getMemo;
@@ -1,34 +1,53 @@
1
- import type { Promisable } from '../typeFest';
1
+ import { MISS } from '..';
2
2
  export type MemoSerializer = (args: any[]) => any;
3
3
  export declare const jsonMemoSerializer: MemoSerializer;
4
4
  export interface MemoCache<KEY = any, VALUE = any> {
5
5
  has: (k: KEY) => boolean;
6
- get: (k: KEY) => VALUE | Error | undefined;
7
- set: (k: KEY, v: VALUE | Error) => void;
8
6
  /**
9
- * Clear is only called when `.dropCache()` is called.
7
+ * `get` return signature doesn't contain `undefined`,
8
+ * because `undefined` is a valid VALUE to store in the Cache.
9
+ * `undefined` does NOT mean cache miss.
10
+ * Cache misses are checked by calling `has` method instead.
11
+ */
12
+ get: (k: KEY) => VALUE;
13
+ set: (k: KEY, v: VALUE) => void;
14
+ /**
15
+ * Clear is only called when `_getMemoCache().clear()` is called.
10
16
  * Otherwise the Cache is "persistent" (never cleared).
11
17
  */
12
18
  clear: () => void;
13
19
  }
14
20
  export interface AsyncMemoCache<KEY = any, VALUE = any> {
15
21
  /**
16
- * `undefined` value returned indicates the ABSENCE of value in the Cache.
17
- * This also means that you CANNOT store `undefined` value in the Cache, as it'll be treated as a MISS.
18
- * You CAN store `null` value instead, it will be treated as a HIT.
22
+ * MISS symbol indicates the ABSENCE of value in the Cache.
23
+ * You can safely store `undefined` or `null` values in the Cache,
24
+ * they will not be interpreted as a cache miss, because there is a special MISS symbol for that.
19
25
  */
20
- get: (k: KEY) => Promisable<VALUE | Error | undefined>;
21
- set: (k: KEY, v: VALUE | Error) => Promisable<void>;
26
+ get: (k: KEY) => Promise<VALUE | typeof MISS>;
27
+ set: (k: KEY, v: VALUE) => Promise<void>;
22
28
  /**
23
- * Clear is only called when `.dropCache()` is called.
29
+ * Clear is only called when `_getAsyncMemo().clear()` is called.
24
30
  * Otherwise the Cache is "persistent" (never cleared).
25
31
  */
26
- clear: () => Promisable<void>;
32
+ clear: () => Promise<void>;
27
33
  }
28
- export declare class MapMemoCache<KEY = any, VALUE = any> implements MemoCache<KEY, VALUE>, AsyncMemoCache<KEY, VALUE> {
34
+ export declare class MapMemoCache<KEY = any, VALUE = any> implements MemoCache<KEY, VALUE> {
29
35
  private m;
30
36
  has(k: KEY): boolean;
31
- get(k: KEY): VALUE | Error | undefined;
32
- set(k: KEY, v: VALUE | Error): void;
37
+ get(k: KEY): VALUE;
38
+ set(k: KEY, v: VALUE): void;
33
39
  clear(): void;
34
40
  }
41
+ /**
42
+ * Implementation of AsyncMemoCache backed by a synchronous Map.
43
+ * Doesn't have a practical use except testing,
44
+ * because the point of AsyncMemoCache is to have an **async** backed cache.
45
+ */
46
+ export declare class MapAsyncMemoCache<KEY = any, VALUE = any> implements AsyncMemoCache<KEY, VALUE> {
47
+ private delay;
48
+ constructor(delay?: number);
49
+ private m;
50
+ get(k: KEY): Promise<VALUE | typeof MISS>;
51
+ set(k: KEY, v: VALUE): Promise<void>;
52
+ clear(): Promise<void>;
53
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MapMemoCache = exports.jsonMemoSerializer = void 0;
3
+ exports.MapAsyncMemoCache = exports.MapMemoCache = exports.jsonMemoSerializer = void 0;
4
4
  const __1 = require("..");
5
5
  const jsonMemoSerializer = args => {
6
6
  if (args.length === 0)
@@ -73,3 +73,29 @@ class MapMemoCache {
73
73
  }
74
74
  }
75
75
  exports.MapMemoCache = MapMemoCache;
76
+ /**
77
+ * Implementation of AsyncMemoCache backed by a synchronous Map.
78
+ * Doesn't have a practical use except testing,
79
+ * because the point of AsyncMemoCache is to have an **async** backed cache.
80
+ */
81
+ class MapAsyncMemoCache {
82
+ constructor(delay = 0) {
83
+ this.delay = delay;
84
+ this.m = new Map();
85
+ }
86
+ async get(k) {
87
+ await (0, __1.pDelay)(this.delay);
88
+ if (!this.m.has(k))
89
+ return __1.MISS;
90
+ return this.m.get(k);
91
+ }
92
+ async set(k, v) {
93
+ await (0, __1.pDelay)(this.delay);
94
+ this.m.set(k, v);
95
+ }
96
+ async clear() {
97
+ await (0, __1.pDelay)(this.delay);
98
+ this.m.clear();
99
+ }
100
+ }
101
+ exports.MapAsyncMemoCache = MapAsyncMemoCache;
@@ -7,5 +7,7 @@ export interface MemoizedFunction {
7
7
  * Only supports Sync functions.
8
8
  * To support Async functions - use _memoFnAsync.
9
9
  * Technically, you can use it with Async functions, but it'll return the Promise without awaiting it.
10
+ *
11
+ * @experimental
10
12
  */
11
13
  export declare function _memoFn<T extends (...args: any[]) => any>(fn: T, opt?: MemoOptions): T & MemoizedFunction;
@@ -6,42 +6,26 @@ const memo_util_1 = require("./memo.util");
6
6
  * Only supports Sync functions.
7
7
  * To support Async functions - use _memoFnAsync.
8
8
  * Technically, you can use it with Async functions, but it'll return the Promise without awaiting it.
9
+ *
10
+ * @experimental
9
11
  */
10
12
  function _memoFn(fn, opt = {}) {
11
- const { logger = console, cacheErrors = true, cacheFactory = () => new memo_util_1.MapMemoCache(), cacheKeyFn = memo_util_1.jsonMemoSerializer, } = opt;
13
+ const { logger = console, cacheFactory = () => new memo_util_1.MapMemoCache(), cacheKeyFn = memo_util_1.jsonMemoSerializer, } = opt;
12
14
  const cache = cacheFactory();
13
15
  const memoizedFn = function (...args) {
14
16
  const ctx = this;
15
17
  const cacheKey = cacheKeyFn(args);
16
- let value;
17
18
  if (cache.has(cacheKey)) {
18
- value = cache.get(cacheKey);
19
- if (value instanceof Error) {
20
- throw value;
21
- }
22
- return value;
19
+ return cache.get(cacheKey);
23
20
  }
21
+ const value = fn.apply(ctx, args);
24
22
  try {
25
- value = fn.apply(ctx, args);
26
- try {
27
- cache.set(cacheKey, value);
28
- }
29
- catch (err) {
30
- logger.error(err);
31
- }
32
- return value;
23
+ cache.set(cacheKey, value);
33
24
  }
34
25
  catch (err) {
35
- if (cacheErrors) {
36
- try {
37
- cache.set(cacheKey, err);
38
- }
39
- catch (err) {
40
- logger.error(err);
41
- }
42
- }
43
- throw err;
26
+ logger.error(err);
44
27
  }
28
+ return value;
45
29
  };
46
30
  Object.assign(memoizedFn, { cache });
47
31
  return memoizedFn;
@@ -4,7 +4,6 @@ export interface MemoizedAsyncFunction {
4
4
  cache: AsyncMemoCache;
5
5
  }
6
6
  /**
7
- * Only supports Sync functions.
8
- * To support Async functions - use _memoFnAsync
7
+ * @experimental
9
8
  */
10
- export declare function _memoFnAsync<T extends (...args: any[]) => Promise<any>>(fn: T, opt?: AsyncMemoOptions): T & MemoizedAsyncFunction;
9
+ export declare function _memoFnAsync<T extends (...args: any[]) => Promise<any>>(fn: T, opt: AsyncMemoOptions): T & MemoizedAsyncFunction;
@@ -1,13 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports._memoFnAsync = void 0;
4
+ const types_1 = require("../types");
4
5
  const memo_util_1 = require("./memo.util");
5
6
  /**
6
- * Only supports Sync functions.
7
- * To support Async functions - use _memoFnAsync
7
+ * @experimental
8
8
  */
9
- function _memoFnAsync(fn, opt = {}) {
10
- const { logger = console, cacheRejections = true, cacheFactory = () => new memo_util_1.MapMemoCache(), cacheKeyFn = memo_util_1.jsonMemoSerializer, } = opt;
9
+ function _memoFnAsync(fn, opt) {
10
+ const { logger = console, cacheFactory = () => new memo_util_1.MapMemoCache(), cacheKeyFn = memo_util_1.jsonMemoSerializer, } = opt;
11
11
  const cache = cacheFactory();
12
12
  const memoizedFn = async function (...args) {
13
13
  const ctx = this;
@@ -19,37 +19,19 @@ function _memoFnAsync(fn, opt = {}) {
19
19
  catch (err) {
20
20
  logger.error(err);
21
21
  }
22
- if (value !== undefined) {
23
- if (value instanceof Error) {
24
- throw value;
25
- }
26
- return value;
27
- }
28
- try {
29
- value = await fn.apply(ctx, args);
30
- void (async () => {
31
- try {
32
- await cache.set(cacheKey, value);
33
- }
34
- catch (err) {
35
- logger.error(err);
36
- }
37
- })();
22
+ if (value !== types_1.MISS) {
38
23
  return value;
39
24
  }
40
- catch (err) {
41
- if (cacheRejections) {
42
- void (async () => {
43
- try {
44
- await cache.set(cacheKey, err);
45
- }
46
- catch (err) {
47
- logger.error(err);
48
- }
49
- })();
25
+ value = await fn.apply(ctx, args);
26
+ void (async () => {
27
+ try {
28
+ await cache.set(cacheKey, value);
50
29
  }
51
- throw err;
52
- }
30
+ catch (err) {
31
+ logger.error(err);
32
+ }
33
+ })();
34
+ return value;
53
35
  };
54
36
  Object.assign(memoizedFn, { cache });
55
37
  return memoizedFn;