UNPKG

4.3 kBJavaScriptView Raw
1import mimicFn from 'mimic-fn';
2const cacheStore = new WeakMap();
3/**
4[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.
5
6@param fn - Function to be memoized.
7
8@example
9```
10import {setTimeout as delay} from 'node:timer/promises';
11import pMemoize from 'p-memoize';
12import got from 'got';
13
14const memoizedGot = pMemoize(got);
15
16await memoizedGot('https://sindresorhus.com');
17
18// This call is cached
19await memoizedGot('https://sindresorhus.com');
20
21await delay(2000);
22
23// This call is not cached as the cache has expired
24await memoizedGot('https://sindresorhus.com');
25```
26*/
27export default function pMemoize(fn, { cacheKey = ([firstArgument]) => firstArgument, cache = new Map(), } = {}) {
28 // Promise objects can't be serialized so we keep track of them internally and only provide their resolved values to `cache`
29 // `Promise<AsyncReturnType<FunctionToMemoize>>` is used instead of `ReturnType<FunctionToMemoize>` because promise properties are not kept
30 const promiseCache = new Map();
31 const memoized = function (...arguments_) {
32 const key = cacheKey(arguments_);
33 if (promiseCache.has(key)) {
34 return promiseCache.get(key);
35 }
36 const promise = (async () => {
37 try {
38 if (cache && await cache.has(key)) {
39 return (await cache.get(key));
40 }
41 const promise = fn.apply(this, arguments_);
42 const result = await promise;
43 try {
44 return result;
45 }
46 finally {
47 if (cache) {
48 await cache.set(key, result);
49 }
50 }
51 }
52 finally {
53 promiseCache.delete(key);
54 }
55 })();
56 promiseCache.set(key, promise);
57 return promise;
58 };
59 mimicFn(memoized, fn, {
60 ignoreNonConfigurable: true,
61 });
62 cacheStore.set(memoized, cache);
63 return memoized;
64}
65/**
66- Only class methods and getters/setters can be memoized, not regular functions (they aren't part of the proposal);
67- Only [TypeScript’s decorators](https://www.typescriptlang.org/docs/handbook/decorators.html#parameter-decorators) are supported, not [Babel’s](https://babeljs.io/docs/en/babel-plugin-proposal-decorators), which use a different version of the proposal;
68- Being an experimental feature, they need to be enabled with `--experimentalDecorators`; follow TypeScript’s docs.
69
70@returns A [decorator](https://github.com/tc39/proposal-decorators) to memoize class methods or static class methods.
71
72@example
73```
74import {pMemoizeDecorator} from 'p-memoize';
75
76class Example {
77 index = 0
78
79 @pMemoizeDecorator()
80 async counter() {
81 return ++this.index;
82 }
83}
84
85class ExampleWithOptions {
86 index = 0
87
88 @pMemoizeDecorator()
89 async counter() {
90 return ++this.index;
91 }
92}
93```
94*/
95export function pMemoizeDecorator(options = {}) {
96 const instanceMap = new WeakMap();
97 return (target, propertyKey, descriptor) => {
98 const input = target[propertyKey]; // eslint-disable-line @typescript-eslint/no-unsafe-assignment
99 if (typeof input !== 'function') {
100 throw new TypeError('The decorated value must be a function');
101 }
102 delete descriptor.value;
103 delete descriptor.writable;
104 descriptor.get = function () {
105 if (!instanceMap.has(this)) {
106 const value = pMemoize(input, options);
107 instanceMap.set(this, value);
108 return value;
109 }
110 return instanceMap.get(this);
111 };
112 };
113}
114/**
115Clear all cached data of a memoized function.
116
117@param fn - Memoized function.
118*/
119export function pMemoizeClear(fn) {
120 if (!cacheStore.has(fn)) {
121 throw new TypeError('Can\'t clear a function that was not memoized!');
122 }
123 const cache = cacheStore.get(fn);
124 if (!cache) {
125 throw new TypeError('Can\'t clear a function that doesn\'t use a cache!');
126 }
127 if (typeof cache.clear !== 'function') {
128 throw new TypeError('The cache Map can\'t be cleared!');
129 }
130 cache.clear();
131}