diff --git a/package-lock.json b/package-lock.json index 217e2db..73e3dfa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -219,25 +219,25 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz", - "integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz", + "integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==", "dev": true, "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.7" + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.10" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.8.tgz", - "integrity": "sha512-TZIQ25pkSoaKEYYaHbbxkfL36GNsQ6iFiBbeuzAkLnXayKR1yP1zFe+NxuZWWsUyvt8icPU9CCq0sgWGXR1GEw==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz", + "integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==", "dev": true, "dependencies": { - "@babel/types": "^7.26.8" + "@babel/types": "^7.26.10" }, "bin": { "parser": "bin/babel-parser.js" @@ -469,14 +469,14 @@ } }, "node_modules/@babel/template": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.8.tgz", - "integrity": "sha512-iNKaX3ZebKIsCvJ+0jd6embf+Aulaa3vNBqZ41kM7iTWjx5qzWKXGHiJUW3+nTpQ18SG11hdF8OAzKrpXkb96Q==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", + "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", "dev": true, "dependencies": { "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.26.8", - "@babel/types": "^7.26.8" + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9" }, "engines": { "node": ">=6.9.0" @@ -510,9 +510,9 @@ } }, "node_modules/@babel/types": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.8.tgz", - "integrity": "sha512-eUuWapzEGWFEpHFxgEaBG8e3n6S8L3MSu0oda755rOfabWPnh0Our1AozNFVUxGFIhbKgd1ksprsoDGMinTOTA==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz", + "integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.25.9", diff --git a/src/execution/memoize.ts b/src/execution/memoize.ts index 41f85a8..769f9a3 100644 --- a/src/execution/memoize.ts +++ b/src/execution/memoize.ts @@ -59,15 +59,23 @@ export function executeMemoize( if (memoizedValue) { return memoizedValue; } else { - const callResponseOrPromise = (execute.bind(this) as typeof execute)(blockFunction.bind(this) as typeof blockFunction, inputs); + const callResponseOrPromise = (execute.bind(this) as typeof execute)( + blockFunction.bind(this) as typeof blockFunction, + inputs, + [], + (res) => res, + (error) => { + throw error; + } + ); memoizationStore.set(inputsHash, callResponseOrPromise); this[memoizationKey].set(options.functionId, memoizationStore); if (callResponseOrPromise instanceof Promise) { - callResponseOrPromise.finally(() => setTimeout(() => memoizationStore.delete(inputsHash), expirationMs)); + return callResponseOrPromise.finally(() => setTimeout(() => memoizationStore.delete(inputsHash), expirationMs)); } else { setTimeout(() => memoizationStore.delete(inputsHash), expirationMs); + return callResponseOrPromise; } - return callResponseOrPromise; } } diff --git a/src/execution/memoizeDecorator.spec.ts b/src/execution/memoizeDecorator.spec.ts index 66847bd..16f7870 100644 --- a/src/execution/memoizeDecorator.spec.ts +++ b/src/execution/memoizeDecorator.spec.ts @@ -70,4 +70,78 @@ describe('memoize decorator', () => { expect(memoizationCheckCount).toBe(1); // 1memoized expect(fib5).toBe(8); }); + + it('should memoize async function results and prevent redundant calls', async () => { + let memoizationCheckCount = 0; + let memoizedCalls = 0; + let totalFunctionCalls = 0; + + class DataService { + @memoize(async (memoContext: MemoizationContext) => { + memoizationCheckCount++; + if (memoContext.isMemoized) { + memoizedCalls++; + } + }) + async fetchData(id: number): Promise { + totalFunctionCalls++; + return new Promise((resolve) => + setTimeout(() => resolve(`Data for ID: ${id}`), 100) + ); + } + + @memoize(async (memoContext: MemoizationContext) => { + memoizationCheckCount++; + if (memoContext.isMemoized) { + memoizedCalls++; + } + }) async throwData(name: string): Promise { + totalFunctionCalls++; + throw new Error(`hello ${name} but I throw!`); + } + } + + const service = new DataService(); + + memoizationCheckCount = 0; + memoizedCalls = 0; + totalFunctionCalls = 0; + + const result1 = await service.fetchData(1); + expect(result1).toBe('Data for ID: 1'); + expect(memoizedCalls).toBe(0); + expect(totalFunctionCalls).toBe(1); + expect(memoizationCheckCount).toBe(1); // Called once + + const result2 = await service.fetchData(1); + expect(result2).toBe('Data for ID: 1'); + expect(memoizedCalls).toBe(1); // Now it should be memoized + expect(totalFunctionCalls).toBe(1); // No new calls + expect(memoizationCheckCount).toBe(2); // Checked twice + + const result3 = await service.fetchData(2); + expect(result3).toBe('Data for ID: 2'); + expect(memoizedCalls).toBe(1); // No extra memoized calls yet + expect(totalFunctionCalls).toBe(2); // New call for different ID + expect(memoizationCheckCount).toBe(3); // Three checks (1st, 2nd for ID 1, and 3rd for ID 2) + + const result4 = await service.fetchData(2); + expect(result4).toBe('Data for ID: 2'); + expect(memoizedCalls).toBe(2); // ID 2 result is now memoized + expect(totalFunctionCalls).toBe(2); // No extra new calls + expect(memoizationCheckCount).toBe(4); // 4 checks in total + + // test memoize a throwing async method + memoizationCheckCount = 0; + memoizedCalls = 0; + totalFunctionCalls = 0; + await Promise.all([ + expect(service.throwData('akram')).rejects.toThrow('hello akram but I throw!'), + expect(service.throwData('akram')).rejects.toThrow('hello akram but I throw!'), + expect(service.throwData('akram')).rejects.toThrow('hello akram but I throw!') + ]); + expect(memoizationCheckCount).toEqual(totalFunctionCalls + memoizedCalls); + expect(memoizedCalls).toEqual(2); + expect(totalFunctionCalls).toBe(1); // No extra new calls + }); }); diff --git a/yarn.lock b/yarn.lock index c741635..2ced76a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -106,19 +106,19 @@ integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== "@babel/helpers@^7.26.7": - version "7.26.7" - resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz" - integrity sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A== + version "7.26.10" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz" + integrity sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g== dependencies: - "@babel/template" "^7.25.9" - "@babel/types" "^7.26.7" + "@babel/template" "^7.26.9" + "@babel/types" "^7.26.10" -"@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": - version "7.26.8" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.26.8.tgz" - integrity sha512-TZIQ25pkSoaKEYYaHbbxkfL36GNsQ6iFiBbeuzAkLnXayKR1yP1zFe+NxuZWWsUyvt8icPU9CCq0sgWGXR1GEw== +"@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": + version "7.26.10" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz" + integrity sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA== dependencies: - "@babel/types" "^7.26.8" + "@babel/types" "^7.26.10" "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -239,14 +239,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.25.9" -"@babel/template@^7.25.9", "@babel/template@^7.26.8", "@babel/template@^7.3.3": - version "7.26.8" - resolved "https://registry.npmjs.org/@babel/template/-/template-7.26.8.tgz" - integrity sha512-iNKaX3ZebKIsCvJ+0jd6embf+Aulaa3vNBqZ41kM7iTWjx5qzWKXGHiJUW3+nTpQ18SG11hdF8OAzKrpXkb96Q== +"@babel/template@^7.26.8", "@babel/template@^7.26.9", "@babel/template@^7.3.3": + version "7.26.9" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz" + integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA== dependencies: "@babel/code-frame" "^7.26.2" - "@babel/parser" "^7.26.8" - "@babel/types" "^7.26.8" + "@babel/parser" "^7.26.9" + "@babel/types" "^7.26.9" "@babel/traverse@^7.25.9", "@babel/traverse@^7.26.8": version "7.26.8" @@ -261,10 +261,10 @@ debug "^4.3.1" globals "^11.1.0" -"@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": - version "7.26.8" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.26.8.tgz" - integrity sha512-eUuWapzEGWFEpHFxgEaBG8e3n6S8L3MSu0oda755rOfabWPnh0Our1AozNFVUxGFIhbKgd1ksprsoDGMinTOTA== +"@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": + version "7.26.10" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz" + integrity sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ== dependencies: "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" @@ -2286,6 +2286,11 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== +fsevents@^2.3.2: + version "2.3.3" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + function-bind@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"