Skip to content

Commit a9732d0

Browse files
committed
Bump version to v1.1.0: Enable smart reuse via getCurrentExecution method to expose the active task's promise for a specific key
1 parent f652d49 commit a9732d0

10 files changed

+318
-88
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ This package extends [zero-overhead-promise-lock](https://www.npmjs.com/package/
2424
- __Key-wise Mutual Exclusiveness :lock:__: Ensures the mutually exclusive execution of tasks **associated with the same key**, either to prevent potential race conditions caused by tasks spanning across multiple event-loop iterations, or to optimize performance.
2525
- __Graceful Teardown :hourglass_flowing_sand:__: Await the completion of all currently pending and executing tasks using the `waitForAllExistingTasksToComplete` method. Example use cases include application shutdowns (e.g., `onModuleDestroy` in Nest.js applications) or maintaining a clear state between unit-tests. A particularly interesting use case is in **batch processing handlers**, where it allows you to signal the completion of all event handlers associated with a batch.
2626
- __"Check-and-Abort" Friendly :see_no_evil:__: The `isActiveKey` getter allows skipping or aborting operations if a lock is already held.
27+
- __Smart Reuse :recycle:__: In scenarios where launching a duplicate task would be wasteful (e.g., refreshing a shared resource or cache), consumers can **await the ongoing execution instead of scheduling a new one**. This is enabled via the `getCurrentExecution` method, which exposes the currently executing task's promise for a specific key, if one is active.
2728
- __Active Key Metrics :bar_chart:__: The `activeKeys` getter provides real-time insight into currently active keys - i.e., keys associated with ongoing tasks.
2829
- __Even-Driven Eviction of Stale Keys__: Automatically removes internal locks for keys with no pending or ongoing tasks, ensuring the lock never retains stale keys.
2930
- __Comprehensive documentation :books:__: Fully documented to provide rich IDE tooltips and enhance the development experience.
@@ -39,6 +40,7 @@ The `ZeroOverheadKeyedLock` class provides the following methods:
3940
* __executeExclusive__: Executes the given task in a controlled manner, once the lock is available. It resolves or rejects when the task finishes execution, returning the task's value or propagating any error it may throw.
4041
* __waitForAllExistingTasksToComplete__: Waits for the completion of all tasks that are *currently* pending or executing. This method is particularly useful in scenarios where it is essential to ensure that all tasks - whether already executing or queued - are fully processed before proceeding. Examples include **application shutdowns** (e.g., `onModuleDestroy` in Nest.js applications) or maintaining a clear state between unit tests.
4142
* __isActiveKey__: Indicates whether the provided key is currently associated with an ongoing task (i.e., the task is not yet completed). This property is particularly useful in "check and abort" scenarios, where an operation should be skipped or aborted if the key is currently held by another task.
43+
* __getCurrentExecution__: Exposes the currently executing task's promise for a specific key, if one is active. This is useful in scenarios where launching a duplicate task would be wasteful - consumers can simply **await the ongoing execution instead**. This feature enhances the lock’s versatility and supports advanced usage patterns.
4244

4345
If needed, refer to the code documentation for a more comprehensive description of each method.
4446

dist/zero-overhead-keyed-promise-lock.d.ts

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,6 @@
6262
export declare class ZeroOverheadKeyedLock<T> {
6363
private readonly _keyToLock;
6464
/**
65-
* activeKeysCount
66-
*
6765
* Returns the number of currently active keys. An active key is associated
6866
* with an ongoing (not yet completed) task.
6967
* The time complexity of this operation is O(1).
@@ -72,8 +70,6 @@ export declare class ZeroOverheadKeyedLock<T> {
7270
*/
7371
get activeKeysCount(): number;
7472
/**
75-
* activeKeys
76-
*
7773
* Returns an array of currently active keys. An active key is associated
7874
* with an ongoing (not yet completed) task.
7975
* The time complexity of this operation is O(active-keys).
@@ -82,8 +78,6 @@ export declare class ZeroOverheadKeyedLock<T> {
8278
*/
8379
get activeKeys(): string[];
8480
/**
85-
* isActiveKey
86-
*
8781
* Indicates whether the provided key is currently associated with an ongoing task
8882
* (i.e., the task is not yet completed).
8983
* The time complexity of this operation is O(1).
@@ -99,8 +93,6 @@ export declare class ZeroOverheadKeyedLock<T> {
9993
*/
10094
isActiveKey(key: string): boolean;
10195
/**
102-
* executeExclusive
103-
*
10496
* Executes a given task exclusively, ensuring that only one task associated with the same key
10597
* can run at a time. It resolves or rejects when the task finishes execution, returning the
10698
* task's value or propagating any error it may throw.
@@ -122,17 +114,46 @@ export declare class ZeroOverheadKeyedLock<T> {
122114
* A keyed lock ensures sequential processing of same-key messages during batch processing, e.g.,
123115
* by using the UserID as the key to avoid concurrent operations on the same user account.
124116
*
125-
* @param key - A non-empty string key associated with the task. For example, the UserID is a
126-
* suitable key to ensure sequential operations on a user account.
127-
* @param criticalTask - The asynchronous task to execute exclusively, ensuring it does not
128-
* overlap with any other execution associated with the same key.
129-
* @throws - Error thrown by the task itself.
117+
* @param key A non-empty string key associated with the task. For example, the UserID is a
118+
* suitable key to ensure sequential operations on a user account.
119+
* @param criticalTask The asynchronous task to execute exclusively, ensuring it does not
120+
* overlap with any other execution associated with the same key.
121+
* @throws Error thrown by the task itself.
130122
* @returns A promise that resolves with the task's return value or rejects with its error.
131123
*/
132124
executeExclusive(key: string, criticalTask: () => Promise<T>): Promise<T>;
133125
/**
134-
* waitForAllExistingTasksToComplete
126+
* Exposes the currently executing task's promise for a specific key, if one is active.
127+
* Note that the returned promise may throw if the active task encounters an error.
128+
*
129+
* ### Smart Reuse
130+
* This property is useful in scenarios where launching a duplicate task is wasteful.
131+
* Instead of scheduling a new task, consumers can **await the ongoing execution** to
132+
* avoid redundant operations.
135133
*
134+
* ### Usage Example
135+
* For example, assume a Kafka message handler receives an event to provision an account
136+
* for a newly onboarded user. Since this operation should not be duplicated, you may
137+
* choose to wait for the current task to complete, if one is already in progress:
138+
* ```
139+
* const ongoing = onboardingLock.getCurrentExecution(userID);
140+
* if (ongoing) {
141+
* await ongoing;
142+
* } else {
143+
* await onboardingLock.executeExclusive(userID, onboardUserTask);
144+
* }
145+
* ```
146+
* #### Important Consideration
147+
* Please note that regardless of the scope of this example, an onboarding operation
148+
* should be implemented in an idempotent manner, even with this optimization.
149+
* In other words, developers should always account for the possibility of redundant executions.
150+
*
151+
* @param key A non-empty string key associated with the task.
152+
* @returns The currently executing task’s promise for the specified key, or `undefined`
153+
* if no task is currently executing for that key.
154+
*/
155+
getCurrentExecution(key: string): Promise<T> | undefined;
156+
/**
136157
* Waits for the completion of all tasks that are *currently* pending or executing.
137158
*
138159
* This method is particularly useful in scenarios where it is essential to ensure that
@@ -157,5 +178,7 @@ export declare class ZeroOverheadKeyedLock<T> {
157178
* of invocation are completed.
158179
*/
159180
waitForAllExistingTasksToComplete(): Promise<void>;
160-
private _getLock;
181+
private _validateKey;
182+
private _getOrCreateLock;
183+
private _getLockIfExists;
161184
}

dist/zero-overhead-keyed-promise-lock.js

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

dist/zero-overhead-keyed-promise-lock.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)