Skip to content

Commit 9df34a5

Browse files
committed
fix(memoize): correctly throw error on memoize when the original method throws
1 parent 0a90f58 commit 9df34a5

File tree

2 files changed

+85
-3
lines changed

2 files changed

+85
-3
lines changed

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
});

0 commit comments

Comments
 (0)