1 | 'use strict';
|
---|
2 | const mimicFn = require("mimic-fn");
|
---|
3 | const mapAgeCleaner = require("map-age-cleaner");
|
---|
4 | const decoratorInstanceMap = new WeakMap();
|
---|
5 | const cacheStore = new WeakMap();
|
---|
6 | /**
|
---|
7 | [Memoize](https://en.wikipedia.org/wiki/Memoization) functions - An optimization used to speed up consecutive function calls by caching the result of calls with identical input.
|
---|
8 |
|
---|
9 | @param fn - Function to be memoized.
|
---|
10 |
|
---|
11 | @example
|
---|
12 | ```
|
---|
13 | import mem = require('mem');
|
---|
14 |
|
---|
15 | let i = 0;
|
---|
16 | const counter = () => ++i;
|
---|
17 | const memoized = mem(counter);
|
---|
18 |
|
---|
19 | memoized('foo');
|
---|
20 | //=> 1
|
---|
21 |
|
---|
22 | // Cached as it's the same arguments
|
---|
23 | memoized('foo');
|
---|
24 | //=> 1
|
---|
25 |
|
---|
26 | // Not cached anymore as the arguments changed
|
---|
27 | memoized('bar');
|
---|
28 | //=> 2
|
---|
29 |
|
---|
30 | memoized('bar');
|
---|
31 | //=> 2
|
---|
32 | ```
|
---|
33 | */
|
---|
34 | const mem = (fn, { cacheKey, cache = new Map(), maxAge } = {}) => {
|
---|
35 | if (typeof maxAge === 'number') {
|
---|
36 | // TODO: Drop after https://github.com/SamVerschueren/map-age-cleaner/issues/5
|
---|
37 | // @ts-expect-error
|
---|
38 | mapAgeCleaner(cache);
|
---|
39 | }
|
---|
40 | const memoized = function (...arguments_) {
|
---|
41 | const key = cacheKey ? cacheKey(arguments_) : arguments_[0];
|
---|
42 | const cacheItem = cache.get(key);
|
---|
43 | if (cacheItem) {
|
---|
44 | return cacheItem.data;
|
---|
45 | }
|
---|
46 | const result = fn.apply(this, arguments_);
|
---|
47 | cache.set(key, {
|
---|
48 | data: result,
|
---|
49 | maxAge: maxAge ? Date.now() + maxAge : Number.POSITIVE_INFINITY
|
---|
50 | });
|
---|
51 | return result;
|
---|
52 | };
|
---|
53 | mimicFn(memoized, fn, {
|
---|
54 | ignoreNonConfigurable: true
|
---|
55 | });
|
---|
56 | cacheStore.set(memoized, cache);
|
---|
57 | return memoized;
|
---|
58 | };
|
---|
59 | /**
|
---|
60 | @returns A [decorator](https://github.com/tc39/proposal-decorators) to memoize class methods or static class methods.
|
---|
61 |
|
---|
62 | @example
|
---|
63 | ```
|
---|
64 | import mem = require('mem');
|
---|
65 |
|
---|
66 | class Example {
|
---|
67 | index = 0
|
---|
68 |
|
---|
69 | @mem.decorator()
|
---|
70 | counter() {
|
---|
71 | return ++this.index;
|
---|
72 | }
|
---|
73 | }
|
---|
74 |
|
---|
75 | class ExampleWithOptions {
|
---|
76 | index = 0
|
---|
77 |
|
---|
78 | @mem.decorator({maxAge: 1000})
|
---|
79 | counter() {
|
---|
80 | return ++this.index;
|
---|
81 | }
|
---|
82 | }
|
---|
83 | ```
|
---|
84 | */
|
---|
85 | mem.decorator = (options = {}) => (target, propertyKey, descriptor) => {
|
---|
86 | const input = target[propertyKey];
|
---|
87 | if (typeof input !== 'function') {
|
---|
88 | throw new TypeError('The decorated value must be a function');
|
---|
89 | }
|
---|
90 | delete descriptor.value;
|
---|
91 | delete descriptor.writable;
|
---|
92 | descriptor.get = function () {
|
---|
93 | if (!decoratorInstanceMap.has(this)) {
|
---|
94 | const value = mem(input, options);
|
---|
95 | decoratorInstanceMap.set(this, value);
|
---|
96 | return value;
|
---|
97 | }
|
---|
98 | return decoratorInstanceMap.get(this);
|
---|
99 | };
|
---|
100 | };
|
---|
101 | /**
|
---|
102 | Clear all cached data of a memoized function.
|
---|
103 |
|
---|
104 | @param fn - Memoized function.
|
---|
105 | */
|
---|
106 | mem.clear = (fn) => {
|
---|
107 | const cache = cacheStore.get(fn);
|
---|
108 | if (!cache) {
|
---|
109 | throw new TypeError('Can\'t clear a function that was not memoized!');
|
---|
110 | }
|
---|
111 | if (typeof cache.clear !== 'function') {
|
---|
112 | throw new TypeError('The cache Map can\'t be cleared!');
|
---|
113 | }
|
---|
114 | cache.clear();
|
---|
115 | };
|
---|
116 | module.exports = mem;
|
---|