softkave-js-utils
Version:
JavaScript & Typescript utility functions, types, and classes
146 lines • 4.54 kB
JavaScript
import { first, noop } from 'lodash-es';
import { getNewId } from '../id/index.js';
import { getDeferredPromise } from '../promise/getDeferredPromise.js';
import { ListenableResource } from './ListenableResource.js';
const kLockQueueItemState = {
waiting: 'w',
waitingOnResolve: 'wr',
};
export class LockStore {
constructor() {
this.locks = {};
this.waiters = [];
this.execNext = (name) => {
const queue = this.getLockQueue(name, true);
const next = first(queue);
const item = next === null || next === void 0 ? void 0 : next.get();
if (!next || !item) {
return;
}
if (item.state === kLockQueueItemState.waitingOnResolve) {
next.listen(this.execLockItem);
}
this.execLockItem(item);
};
this.execLockItem = (item) => {
if ((item === null || item === void 0 ? void 0 : item.state) === kLockQueueItemState.waiting) {
item.resolveFn();
return true;
}
return false;
};
}
run(name, fn) {
const item = this.queue(name);
return this.__run(name, item, fn);
}
has(name) {
var _a;
return !!((_a = this.getLockQueue(name, false)) === null || _a === void 0 ? void 0 : _a.length);
}
wait(params) {
var _a, _b, _c;
const { name, timeoutMs } = params;
const queueLength = (_b = (_a = this.getLockQueue(name, false)) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0;
const remaining = (_c = params.remaining) !== null && _c !== void 0 ? _c : queueLength;
if (queueLength === 0 || remaining <= 0) {
return Promise.resolve();
}
const p = getDeferredPromise();
const waiter = {
remaining,
resolveFn: p.resolve,
timeoutId: undefined,
};
this.waiters.push(waiter);
if (timeoutMs) {
waiter.timeoutId = setTimeout(() => {
p.reject(new Error('Timeout'));
this.waiters = this.waiters.filter(w => w !== waiter);
}, timeoutMs);
}
return p.promise;
}
dispose() {
this.waiters.forEach(waiter => {
if (waiter.timeoutId) {
clearTimeout(waiter.timeoutId);
}
});
}
async __run(name, item, fn) {
await this.acquire(name, item);
try {
return await fn();
}
finally {
this.release(name);
}
}
queue(name) {
const queue = this.getLockQueue(name, true);
const item = new ListenableResource({
state: kLockQueueItemState.waitingOnResolve,
resolveFn: noop,
});
queue.push(item);
return item;
}
acquire(name, item) {
const p = new Promise(resolve => {
item.set({
state: kLockQueueItemState.waiting,
resolveFn: resolve,
});
});
setTimeout(() => this.execNext(name), 0);
return p;
}
release(name) {
const queue = this.getLockQueue(name, true);
queue.shift();
this.waiters = this.waiters.filter(w => {
w.remaining--;
const isWaitDone = w.remaining <= 0;
if (isWaitDone) {
if (w.timeoutId) {
clearTimeout(w.timeoutId);
}
w.resolveFn();
}
return !isWaitDone;
});
this.execNext(name);
}
getLockQueue(name, init) {
let queue = this.locks[name];
if (!queue && init) {
queue = this.locks[name] = [];
}
return queue;
}
}
export class LockableResource {
constructor(locks, resource, name = getNewId()) {
this.locks = locks;
this.resource = resource;
this.name = name;
}
async run(fn) {
await this.locks.run(this.name, async () => {
const newData = await fn(this.resource);
if (newData) {
this.resource = newData;
}
});
}
}
export class SingleInstanceRunner {
static make(locks, opts) {
return async (...args) => {
const id = opts.instanceSpecifier(...args);
return await locks.run(id, () => opts.fn(...args));
};
}
}
//# sourceMappingURL=LockStore.js.map