You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

80 lines
1.8 KiB
JavaScript

function makeWaiter() {
let resolve, reject;
const wait = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return {wait, resolve, reject};
}
class AsyncLock {
#locked = false;
#queue = [];
get locked() {
return this.#locked;
}
async runExclusive(callback, timeout = -1) {
const release = await this.acquire(timeout);
try {
return await callback();
} finally {
release();
}
}
async acquire(timeout = -1) {
if (!this.#locked) {
this.#locked = true;
return this.#newReleaser();
}
if (timeout === 0)
throw new TimeoutError();
let {wait, resolve, reject} = makeWaiter();
if (timeout > 0) {
const timer = new AsyncTimer(timeout, () => reject(new TimeoutError()));
timer.start();
this.#queue.push({ resolve, reject, timer });
} else {
this.#queue.push({ resolve, reject, timer: undefined });
}
this.#dispatch();
return await wait;
}
#dispatch() {
if (!this.#locked && this.#queue.length) {
const entry = this.#queue.shift();
this.#locked = true;
if (entry.timer) clearTimeout(entry.timer);
entry.resolve(this.#newReleaser());
}
}
#newReleaser() {
let called = false;
return () => {
if (called) return;
called = true;
this.#release();
};
}
#release() {
if (this.#locked)
this.#locked = false;
this.#dispatch();
}
cancel() {
this.#queue.forEach((entry) => entry.reject(this._cancelError));
this.#queue = [];
}
}