Skip to content

Commit 8da26bd

Browse files
authored
Merge pull request #64 from tabkram/feature/memoize
fix(memoize): correctly throw error on memoize when the original method throws
2 parents 0a90f58 + 7d1536f commit 8da26bd

File tree

4 files changed

+127
-40
lines changed

4 files changed

+127
-40
lines changed

package-lock.json

Lines changed: 17 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/execution/memoize.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,23 @@ export function executeMemoize<O>(
5959
if (memoizedValue) {
6060
return memoizedValue;
6161
} else {
62-
const callResponseOrPromise = (execute.bind(this) as typeof execute)(blockFunction.bind(this) as typeof blockFunction, inputs);
62+
const callResponseOrPromise = (execute.bind(this) as typeof execute)(
63+
blockFunction.bind(this) as typeof blockFunction,
64+
inputs,
65+
[],
66+
(res) => res,
67+
(error) => {
68+
throw error;
69+
}
70+
);
6371
memoizationStore.set(inputsHash, callResponseOrPromise);
6472
this[memoizationKey].set(options.functionId, memoizationStore);
6573

6674
if (callResponseOrPromise instanceof Promise) {
67-
callResponseOrPromise.finally(() => setTimeout(() => memoizationStore.delete(inputsHash), expirationMs));
75+
return callResponseOrPromise.finally(() => setTimeout(() => memoizationStore.delete(inputsHash), expirationMs));
6876
} else {
6977
setTimeout(() => memoizationStore.delete(inputsHash), expirationMs);
78+
return callResponseOrPromise;
7079
}
71-
return callResponseOrPromise;
7280
}
7381
}

src/execution/memoizeDecorator.spec.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,78 @@ describe('memoize decorator', () => {
7070
expect(memoizationCheckCount).toBe(1); // 1memoized
7171
expect(fib5).toBe(8);
7272
});
73+
74+
it('should memoize async function results and prevent redundant calls', async () => {
75+
let memoizationCheckCount = 0;
76+
let memoizedCalls = 0;
77+
let totalFunctionCalls = 0;
78+
79+
class DataService {
80+
@memoize(async (memoContext: MemoizationContext<string>) => {
81+
memoizationCheckCount++;
82+
if (memoContext.isMemoized) {
83+
memoizedCalls++;
84+
}
85+
})
86+
async fetchData(id: number): Promise<string> {
87+
totalFunctionCalls++;
88+
return new Promise((resolve) =>
89+
setTimeout(() => resolve(`Data for ID: ${id}`), 100)
90+
);
91+
}
92+
93+
@memoize(async (memoContext: MemoizationContext<string>) => {
94+
memoizationCheckCount++;
95+
if (memoContext.isMemoized) {
96+
memoizedCalls++;
97+
}
98+
}) async throwData(name: string): Promise<string> {
99+
totalFunctionCalls++;
100+
throw new Error(`hello ${name} but I throw!`);
101+
}
102+
}
103+
104+
const service = new DataService();
105+
106+
memoizationCheckCount = 0;
107+
memoizedCalls = 0;
108+
totalFunctionCalls = 0;
109+
110+
const result1 = await service.fetchData(1);
111+
expect(result1).toBe('Data for ID: 1');
112+
expect(memoizedCalls).toBe(0);
113+
expect(totalFunctionCalls).toBe(1);
114+
expect(memoizationCheckCount).toBe(1); // Called once
115+
116+
const result2 = await service.fetchData(1);
117+
expect(result2).toBe('Data for ID: 1');
118+
expect(memoizedCalls).toBe(1); // Now it should be memoized
119+
expect(totalFunctionCalls).toBe(1); // No new calls
120+
expect(memoizationCheckCount).toBe(2); // Checked twice
121+
122+
const result3 = await service.fetchData(2);
123+
expect(result3).toBe('Data for ID: 2');
124+
expect(memoizedCalls).toBe(1); // No extra memoized calls yet
125+
expect(totalFunctionCalls).toBe(2); // New call for different ID
126+
expect(memoizationCheckCount).toBe(3); // Three checks (1st, 2nd for ID 1, and 3rd for ID 2)
127+
128+
const result4 = await service.fetchData(2);
129+
expect(result4).toBe('Data for ID: 2');
130+
expect(memoizedCalls).toBe(2); // ID 2 result is now memoized
131+
expect(totalFunctionCalls).toBe(2); // No extra new calls
132+
expect(memoizationCheckCount).toBe(4); // 4 checks in total
133+
134+
// test memoize a throwing async method
135+
memoizationCheckCount = 0;
136+
memoizedCalls = 0;
137+
totalFunctionCalls = 0;
138+
await Promise.all([
139+
expect(service.throwData('akram')).rejects.toThrow('hello akram but I throw!'),
140+
expect(service.throwData('akram')).rejects.toThrow('hello akram but I throw!'),
141+
expect(service.throwData('akram')).rejects.toThrow('hello akram but I throw!')
142+
]);
143+
expect(memoizationCheckCount).toEqual(totalFunctionCalls + memoizedCalls);
144+
expect(memoizedCalls).toEqual(2);
145+
expect(totalFunctionCalls).toBe(1); // No extra new calls
146+
});
73147
});

yarn.lock

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -106,19 +106,19 @@
106106
integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==
107107

108108
"@babel/helpers@^7.26.7":
109-
version "7.26.7"
110-
resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz"
111-
integrity sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==
109+
version "7.26.10"
110+
resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz"
111+
integrity sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==
112112
dependencies:
113-
"@babel/template" "^7.25.9"
114-
"@babel/types" "^7.26.7"
113+
"@babel/template" "^7.26.9"
114+
"@babel/types" "^7.26.10"
115115

116-
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.26.8":
117-
version "7.26.8"
118-
resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.26.8.tgz"
119-
integrity sha512-TZIQ25pkSoaKEYYaHbbxkfL36GNsQ6iFiBbeuzAkLnXayKR1yP1zFe+NxuZWWsUyvt8icPU9CCq0sgWGXR1GEw==
116+
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.26.8", "@babel/parser@^7.26.9":
117+
version "7.26.10"
118+
resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz"
119+
integrity sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==
120120
dependencies:
121-
"@babel/types" "^7.26.8"
121+
"@babel/types" "^7.26.10"
122122

123123
"@babel/plugin-syntax-async-generators@^7.8.4":
124124
version "7.8.4"
@@ -239,14 +239,14 @@
239239
dependencies:
240240
"@babel/helper-plugin-utils" "^7.25.9"
241241

242-
"@babel/template@^7.25.9", "@babel/template@^7.26.8", "@babel/template@^7.3.3":
243-
version "7.26.8"
244-
resolved "https://registry.npmjs.org/@babel/template/-/template-7.26.8.tgz"
245-
integrity sha512-iNKaX3ZebKIsCvJ+0jd6embf+Aulaa3vNBqZ41kM7iTWjx5qzWKXGHiJUW3+nTpQ18SG11hdF8OAzKrpXkb96Q==
242+
"@babel/template@^7.26.8", "@babel/template@^7.26.9", "@babel/template@^7.3.3":
243+
version "7.26.9"
244+
resolved "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz"
245+
integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==
246246
dependencies:
247247
"@babel/code-frame" "^7.26.2"
248-
"@babel/parser" "^7.26.8"
249-
"@babel/types" "^7.26.8"
248+
"@babel/parser" "^7.26.9"
249+
"@babel/types" "^7.26.9"
250250

251251
"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.8":
252252
version "7.26.8"
@@ -261,10 +261,10 @@
261261
debug "^4.3.1"
262262
globals "^11.1.0"
263263

264-
"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.9", "@babel/types@^7.26.7", "@babel/types@^7.26.8", "@babel/types@^7.3.3":
265-
version "7.26.8"
266-
resolved "https://registry.npmjs.org/@babel/types/-/types-7.26.8.tgz"
267-
integrity sha512-eUuWapzEGWFEpHFxgEaBG8e3n6S8L3MSu0oda755rOfabWPnh0Our1AozNFVUxGFIhbKgd1ksprsoDGMinTOTA==
264+
"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.9", "@babel/types@^7.26.10", "@babel/types@^7.26.8", "@babel/types@^7.26.9", "@babel/types@^7.3.3":
265+
version "7.26.10"
266+
resolved "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz"
267+
integrity sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==
268268
dependencies:
269269
"@babel/helper-string-parser" "^7.25.9"
270270
"@babel/helper-validator-identifier" "^7.25.9"
@@ -2286,6 +2286,11 @@ fs.realpath@^1.0.0:
22862286
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
22872287
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
22882288

2289+
fsevents@^2.3.2:
2290+
version "2.3.3"
2291+
resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz"
2292+
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
2293+
22892294
function-bind@^1.1.2:
22902295
version "1.1.2"
22912296
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"

0 commit comments

Comments
 (0)