fast-typescript-memoize
Advanced tools
Comparing version 1.0.4 to 1.1.0
/** | ||
* A @Memoize() decorator similar to | ||
* https://www.npmjs.com/package/typescript-memoize. Differences: | ||
* 1. If used to memoize async functions, it clears the memoize cache if the | ||
* promise gets rejected (i.e. it doesn't memoize exceptions in async | ||
* functions). | ||
* 2. Stronger typing for internal code. | ||
* Additional options for `@Memoize()` decorator. | ||
*/ | ||
export declare function Memoize(hasher?: (...args: any[]) => unknown): (_target: object, _propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<any>) => void; | ||
export interface MemoizeOptions { | ||
/** Defaults to `true`. If true, rejected Promises returned from an async | ||
* method will be removed from the cache as soon as the method finishes. */ | ||
clearOnReject?: boolean; | ||
/** Defaults to `false`. If true, successfully resolved Promises returned from | ||
* an async method will be removed from the cache as soon as the method | ||
* finishes. This is a convenient mode for the cases when we want to coalesce | ||
* multiple parallel executions of some method (e.g. when there is a burst of | ||
* runs), but we don't want to prevent the method from further running. */ | ||
clearOnResolve?: boolean; | ||
} | ||
/** | ||
* Remembers the returned value of a decorated method or getter in a hidden | ||
* `this` object's property, so next time the method is called, the value will | ||
* be returned immediately, without re-executing the method. This also works for | ||
* async methods which return a Promise: in this case, multiple parallel calls | ||
* to that method will coalesce into one call. | ||
* | ||
* All `@Memoize()` calls, for both methods and getters, accept a hasher | ||
* function with the same list of arguments as the method itself (or, in case of | ||
* a getter, with no arguments). The slot for the saved value will depend on the | ||
* value returned by the hasher. | ||
*/ | ||
export declare function Memoize<TThis, TValue>(hasher: TValue extends (...args: never[]) => unknown ? (this: TThis, ...args: Parameters<TValue>) => unknown : (this: TThis) => unknown, options?: MemoizeOptions): (target: TThis, propertyKey: string | symbol, descriptor: { | ||
value?: TValue; | ||
}) => void; | ||
/** | ||
* Remembers the returned value of a decorated method or getter in a hidden | ||
* `this` object's property, so next time the method is called, the value will | ||
* be returned immediately, without re-executing the method. This also works for | ||
* async methods which return a Promise: in this case, multiple parallel calls | ||
* to that method will coalesce into one call. | ||
* | ||
* Almost all `@Memoize()` calls may also omit the hasher function. Then, for | ||
* 0-argument methods or getters, the slot for the saved value will be fixed. | ||
* For 1-argument methods, the slot will be chosen based on that single | ||
* argument's value. For methods with 2+ arguments, you must provide your own | ||
* hasher function. | ||
*/ | ||
export declare function Memoize<TThis, TValue>(options?: MemoizeOptions): (target: TThis, propertyKey: string | symbol, descriptor: { | ||
value?: TValue; | ||
}) => ((a1: unknown, a2: unknown, ...args: unknown[]) => never) extends TValue ? "provide-hasher-when-method-has-more-than-one-arg" : void; | ||
//# sourceMappingURL=Memoize.d.ts.map |
@@ -5,19 +5,16 @@ "use strict"; | ||
/** | ||
* A @Memoize() decorator similar to | ||
* https://www.npmjs.com/package/typescript-memoize. Differences: | ||
* 1. If used to memoize async functions, it clears the memoize cache if the | ||
* promise gets rejected (i.e. it doesn't memoize exceptions in async | ||
* functions). | ||
* 2. Stronger typing for internal code. | ||
* A @Memoize() decorator implementation, inspired by: | ||
* https://www.npmjs.com/package/typescript-memoize. | ||
*/ | ||
function Memoize(hasher) { | ||
function Memoize(a1, a2) { | ||
const [hasher, options] = typeof a1 === "function" ? [a1, a2] : [undefined, a1]; | ||
return (_target, _propertyKey, descriptor) => { | ||
if (descriptor.value !== null && descriptor.value !== undefined) { | ||
descriptor.value = buildNewMethod(descriptor.value, hasher); | ||
if (typeof descriptor.value === "function") { | ||
descriptor.value = buildNewMethod(descriptor.value, hasher, options); | ||
} | ||
else if (descriptor.get) { | ||
descriptor.get = buildNewMethod(descriptor.get, hasher); | ||
descriptor.get = buildNewMethod(descriptor.get, hasher, options); | ||
} | ||
else { | ||
throw "Only put a @Memoize() decorator on a method or get accessor."; | ||
throw "Only put @Memoize() decorator on a method or get accessor."; | ||
} | ||
@@ -32,3 +29,6 @@ }; | ||
*/ | ||
function buildNewMethod(origMethod, hasher) { | ||
function buildNewMethod(origMethod, hasher, { clearOnReject, clearOnResolve } = { | ||
clearOnReject: true, | ||
clearOnResolve: false, | ||
}) { | ||
const identifier = ++counter; | ||
@@ -55,5 +55,8 @@ return function (...args) { | ||
value = origMethod.apply(this, args); | ||
if (value instanceof Promise) { | ||
if (clearOnReject && value instanceof Promise) { | ||
value = value.catch(deleteMapKeyAndRethrow.bind(undefined, map, hashKey)); | ||
} | ||
if (clearOnResolve && value instanceof Promise) { | ||
value = value.then(deleteMapKeyAndReturn.bind(undefined, map, hashKey)); | ||
} | ||
map.set(hashKey, value); | ||
@@ -69,7 +72,10 @@ } | ||
value = origMethod.apply(this, args); | ||
if (value instanceof Promise) { | ||
value = value.catch(deleteObjKeyAndRethrow.bind(undefined, this, propValName)); | ||
if (clearOnReject && value instanceof Promise) { | ||
value = value.catch(deleteObjPropAndRethrow.bind(undefined, this, propValName)); | ||
} | ||
if (clearOnResolve && value instanceof Promise) { | ||
value = value.then(deleteObjPropAndReturn.bind(undefined, this, propValName)); | ||
} | ||
Object.defineProperty(this, propValName, { | ||
configurable: true, | ||
configurable: true, // to be able to remove it | ||
enumerable: false, | ||
@@ -96,6 +102,22 @@ writable: false, | ||
*/ | ||
function deleteObjKeyAndRethrow(obj, key, e) { | ||
function deleteObjPropAndRethrow(obj, key, e) { | ||
delete obj[key]; | ||
throw e; | ||
} | ||
/** | ||
* A helper function to just not use "=>" closures and thus control, which | ||
* variables will be retained from garbage collection. | ||
*/ | ||
function deleteMapKeyAndReturn(map, key, value) { | ||
map.delete(key); | ||
return value; | ||
} | ||
/** | ||
* A helper function to just not use "=>" closures and thus control, which | ||
* variables will be retained from garbage collection. | ||
*/ | ||
function deleteObjPropAndReturn(obj, key, value) { | ||
delete obj[key]; | ||
return value; | ||
} | ||
//# sourceMappingURL=Memoize.js.map |
{ | ||
"name": "fast-typescript-memoize", | ||
"version": "1.0.4", | ||
"version": "1.1.0", | ||
"description": "Fast memoization decorator and other helpers with 1st class support for Promises.", | ||
@@ -31,15 +31,11 @@ "homepage": "https://github.com/dimikot/fast-typescript-memoize#readme", | ||
}, | ||
"dependencies": {}, | ||
"dependencies": { | ||
"typescript": "^5.0.4" | ||
}, | ||
"devDependencies": { | ||
"@types/jest": "^29.4.0", | ||
"@types/lodash": "^4.13.1", | ||
"@types/jest": "^29.5.12", | ||
"delay": "^4.4.1", | ||
"jest": "^29.5.0", | ||
"lodash": "^4.13.1", | ||
"ts-jest": "^29.0.5", | ||
"typescript": "^4.9.5" | ||
}, | ||
"peerDependencies": { | ||
"lodash": "^4.13.1" | ||
"jest": "^29.7.0", | ||
"ts-jest": "^29.1.2" | ||
} | ||
} |
# fast-typescript-memoize: fast memoization decorator and other helpers with 1st class support for Promises | ||
## @Memoize() decorator | ||
## `@Memoize()` decorator | ||
A `@Memoize()` TypeScript decorator similar to | ||
Remembers the returned value of a decorated method or getter in a hidden `this` | ||
object's property, so next time the method is called, the value will be returned | ||
immediately, without re-executing the method. This also works for async methods | ||
which return a Promise: in this case, multiple parallel calls to that method | ||
will coalesce into one call. | ||
To work properly, requires TypeScript v5+. | ||
The idea of `@Memoize()` decorator is brought from | ||
[typescript-memoize](https://www.npmjs.com/package/typescript-memoize). | ||
Differences: | ||
1. If used to memoize async functions, it clears the memoize cache if the | ||
promise gets rejected (i.e. it doesn't memoize exceptions in async | ||
functions). | ||
2. Stronger typing for internal code. | ||
3. Does not support any notion of expiration. | ||
1. If used to memoize async methods, by default (and when | ||
`clearOnResolve=true`), it clears the memoize cache as soon as the Promise | ||
gets rejected (i.e. it doesn't memoize exceptions in async methods). Parallel | ||
async calls to the same method will still be coalesced into one call though | ||
(until the Promise rejects). | ||
2. A special mode is added, `clearOnResolve`. If `true`, successfully resolved | ||
Promises returned from an async method will be removed from the cache as soon | ||
as the method finishes. This is a convenient mode for the cases when we want | ||
to coalesce multiple parallel executions of some method (e.g. when there is a | ||
burst of runs), but we don't want to prevent the method from further running. | ||
3. Strong typing for the optional hasher handler, including types of arguments | ||
and even the type of `this`. | ||
4. Does not support any notion of expiration. | ||
```ts | ||
@@ -22,2 +36,3 @@ import { Memoize } from "fast-typescript-memoize"; | ||
private count = 0; | ||
private some = 42; | ||
@@ -34,3 +49,3 @@ @Memoize() | ||
@Memoize((arg1: string, arg2: number) => `${arg1}#${arg2}`) | ||
@Memoize((arg1, arg2) => `${arg1}#${arg2}`) | ||
method2(arg1: string, arg2: number) { | ||
@@ -40,4 +55,9 @@ return count++; | ||
@Memoize(function (arg1, arg2) { return `${this.some}:${arg1}#${arg2}`; }) | ||
method3(arg1: string, arg2: number) { | ||
return count++; | ||
} | ||
@Memoize() | ||
asyncMethod(arg: string) { | ||
async asyncMethod(arg: string) { | ||
count++; | ||
@@ -48,5 +68,12 @@ if (arg == "ouch") { | ||
} | ||
@Memoize({ clearOnResolve: true }) | ||
async asyncCoalescingMethod(arg: string) { | ||
await delay(100); | ||
count++; | ||
} | ||
} | ||
const obj = new Class(); | ||
obj.method0(); // count is incremented | ||
@@ -62,2 +89,5 @@ obj.method0(); // count is NOT incremented | ||
obj.method3("abc", 42); // count is incremented (strongly typed `this`) | ||
obj.method3("abc", 42); // count is NOT incremented | ||
await asyncMethod("ok"); // count is incremented | ||
@@ -67,2 +97,10 @@ await asyncMethod("ok"); // count is NOT incremented | ||
await asyncMethod("ouch"); // count is incremented, exception is thrown | ||
await asyncCoalescingMethod("ok"); // count is incremented | ||
await asyncCoalescingMethod("ok"); // count is incremented again | ||
const [c1, c2] = await Promise.all([ | ||
asyncCoalescingMethod("ok"), // count is incremented | ||
asyncCoalescingMethod("ok"), // not incremented! coalescing parallel calls | ||
]); | ||
assert(c1 === c2); | ||
``` | ||
@@ -69,0 +107,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
28462
4
24
299
160
0
+ Addedtypescript@^5.0.4
+ Addedtypescript@5.4.5(transitive)
- Removedlodash@4.17.21(transitive)