reentrant-lock
Advanced tools
Comparing version 2.0.1 to 3.0.0
@@ -1,9 +0,12 @@ | ||
declare type ReentrantLockReleaser = () => void; | ||
interface UnlockFn { | ||
(): void; | ||
} | ||
export declare class ReentrantLock { | ||
private current?; | ||
private last?; | ||
private next; | ||
acquire(): Promise<ReentrantLockReleaser>; | ||
private readonly chainNoop; | ||
private readonly chainNext; | ||
acquire(): Promise<UnlockFn>; | ||
lock<T>(op: () => Promise<T>): Promise<T>; | ||
} | ||
export {}; |
33
index.js
@@ -6,8 +6,8 @@ "use strict"; | ||
constructor() { | ||
this.next = () => { | ||
this.chainNoop = () => void (0); | ||
this.chainNext = () => { | ||
if (this.current === this.last) { | ||
this.current = undefined; | ||
this.last = undefined; | ||
this.current = this.last = undefined; | ||
} | ||
else if (this.current && this.current.next) { | ||
else { | ||
this.current.next(); | ||
@@ -19,13 +19,20 @@ } | ||
if (!this.last) { | ||
/* c8 ignore next */ | ||
this.current = this.last = () => null; | ||
return Promise.resolve(this.next); | ||
let noop = this.current = this.last = this.chainNoop; | ||
return Promise.resolve(() => { | ||
if (noop) { | ||
noop = undefined; | ||
this.chainNext(); | ||
} | ||
}); | ||
} | ||
else { | ||
return new Promise((resolve, reject) => { | ||
const lockChainItem = () => { | ||
this.current = lockChainItem; | ||
resolve(this.next); | ||
return new Promise((resolve) => { | ||
let lockChainFn = () => { | ||
if (lockChainFn) { | ||
this.current = lockChainFn; | ||
lockChainFn = undefined; | ||
resolve(this.chainNext); | ||
} | ||
}; | ||
this.last = this.last.next = lockChainItem; | ||
this.last = this.last.next = lockChainFn; | ||
}); | ||
@@ -35,5 +42,5 @@ } | ||
lock(op) { | ||
return this.acquire().then(releaser => op().finally(releaser)); | ||
return this.acquire().then(unlock => op().finally(unlock)); | ||
} | ||
} | ||
exports.ReentrantLock = ReentrantLock; |
{ | ||
"name": "reentrant-lock", | ||
"version": "2.0.1", | ||
"version": "3.0.0", | ||
"description": "Lock blocks of code to prevent concurrent execution", | ||
@@ -30,8 +30,8 @@ "keywords": [ | ||
"devDependencies": { | ||
"@types/tape": "^4.13.2", | ||
"c8": "^7.11.3", | ||
"tape": "^5.5.3", | ||
"ts-node": "^10.8.1", | ||
"typescript": "^4.7.3" | ||
"@types/tape": "^5.6.1", | ||
"c8": "^8.0.1", | ||
"tape": "^5.6.6", | ||
"ts-node": "^10.9.1", | ||
"typescript": "^5.2.2" | ||
} | ||
} |
108
readme.md
# ReentrantLock | ||
## Lock mechanism for promises, using a linked list like implementation. | ||
This module is intended to lock promises, forcing some code block to be executed complete before while other contexts wait for the lock to be released. | ||
The module has been written without using arrays using a linked function chain. | ||
### Lightweight lock mechanism for promises, using a linked list implementation. | ||
You can use same lock multiple times to lock different code blocks. | ||
Lightweight, dependency free, promise lock mechanism. | ||
It allows 2 semantics: | ||
* Passing an async callback to the lock method | ||
It's implemented without relying on arrays using a linked function chain. | ||
The implementation can be reviewed in a moment, it is under 50 lines. | ||
## Examples: | ||
The library can be used to avoid concurrent execution of an async block that contains await calls. | ||
For example a token refresh http request than can be invoked from different promises. | ||
The next example summarized how to reduce the possible overload caused by that common situation: | ||
```js | ||
const promiseLock = new ReentrantLock(); | ||
const refreshLocker = new ReentrantLock(); | ||
let accessToken = "..." | ||
let refreshToken = "..." | ||
function isTokenExpired() { | ||
... // return bool | ||
} | ||
async function refreshToken() { | ||
// We lock the refresh block execution | ||
await refreshLocker.lock(async ()=>{ | ||
// By checking the state again after the lock was released we know the previous consumer has already refreshed the token | ||
if(!isTokenExpired()) { | ||
// So we can abort the execution avoiding an unnecessary server call | ||
return; | ||
} | ||
let newCredentials = await fetch(...); | ||
... | ||
}); | ||
} | ||
export async function myApiCall1() { | ||
if(isTokenExpired()) { | ||
await refreshToken(); | ||
} | ||
... // consume the api with a valid token | ||
} | ||
export async function myApiCall2() { | ||
if(isTokenExpired()) { | ||
await refreshToken(); | ||
} | ||
... // consume the api with a valid token | ||
} | ||
... | ||
// Somewhere in the code | ||
await Promise.all([myApiCall1(), myApiCall2()]); | ||
... | ||
``` | ||
Also can be used to chain async executions that are not awaited and need to be non concurrent. | ||
For example to display messages in an UI one after another like in the next basic example: | ||
```js | ||
const tooltipLocker = new ReentrantLock(); | ||
function displayTextForAWhile(text) { | ||
const paragraph = document.createElement("p"); | ||
paragraph.textContent = text; | ||
tooltipLocker.lock((unlock) => { | ||
document.body.appendChild(elemDiv); | ||
setTimeout(() => { | ||
paragraph.remove(); | ||
unlock(); | ||
}, 2000); | ||
}) | ||
.then(() => console.debug(`Text '${text}' displayed at ${new Date().getTime()}`)) | ||
.catch((err) => ...); | ||
} | ||
displayTextForAWhile("One tip"); | ||
displayTextForAWhile("Other tip"); | ||
displayTextForAWhile("Another one"); | ||
``` | ||
# Semantic | ||
The library allows two semantics: | ||
Passing an async callback to the lock method: | ||
```js | ||
const promiseLocker = new ReentrantLock(); | ||
async function () { | ||
... | ||
await promiseLock.lock(async ()=>{ | ||
await promiseLocker.lock(async ()=>{ | ||
// your code block | ||
@@ -21,12 +102,15 @@ }); | ||
``` | ||
* Adquire the lock (you need to release it always) | ||
Acquire and release the lock: | ||
```js | ||
const promiseLock = new ReentrantLock(); | ||
const promiseLocker = new ReentrantLock(); | ||
async function () { | ||
... | ||
const releaseLockFn = await promiseLock.acquire(); | ||
const unlockFn = await promiseLocker.acquire(); | ||
try{ | ||
// your code block | ||
} finally { | ||
releaseLockFn(); | ||
// unlock next consumer | ||
unlockFn(); | ||
} | ||
@@ -33,0 +117,0 @@ ... |
5282
55
118