Decorators
How to apply @asyncmux and @asyncmux.readonly to class methods for per-instance mutual exclusion.
Protect class methods
@asyncmux and @asyncmux.readonly are applied to class methods that return a Promise. The lock target is the instance the method runs on (this). The lock is released automatically when the method finishes, so it can never leak.
@asyncmux — Write lock
Methods decorated with @asyncmux run serially on the same instance. While one call is in progress, other calls wait.
import { asyncmux } from "asyncmux";
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
class Counter {
#count = 0;
@asyncmux
async increment() {
const current = this.#count;
await sleep(10);
this.#count = current + 1;
}
}
Even when increment() is called concurrently, updates to #count are never lost.
const counter = new Counter();
// The two calls run serially.
await Promise.all([counter.increment(), counter.increment()]);
@asyncmux.readonly — Read lock
Methods decorated with @asyncmux.readonly can run concurrently with each other. While a method decorated with @asyncmux is running, they wait for it to finish.
import { asyncmux } from "asyncmux";
class DataStore {
#data = new Map<string, string>();
@asyncmux
async update(key: string, value: string) {
// Writes are exclusive.
this.#data.set(key, value);
}
@asyncmux.readonly
async read(key: string) {
// Reads run concurrently with each other, exclusively with writes.
return this.#data.get(key);
}
}
Lock scope
- Locks are independent per instance. Methods on different instances never affect each other.
- The lock is released even when the method throws.
- The lock is held only while the method is running. It never remains after the method returns.
Reentrancy
Calling a method on the same instance while holding its lock throws ReentrantLockError to prevent deadlocks. Only reentrancy between read locks is allowed, as a shared lock.
class Service {
@asyncmux
async outer() {
// Calling a method on the same instance while holding the write lock fails.
await this.inner();
}
@asyncmux
async inner() {}
}
See Concurrency Model for the detailed detection rules.
Caveats
- You cannot pass an
AbortSignalto decorators. To abort a wait, use the function API. - In environments without Stage 3 decorator support,
DecoratorSupportErroris thrown. - Decorators can only be applied to class methods. They cannot be used on plain functions or arrow function properties.