Skip to main content
A mutual exclusion lock that ensures only one task accesses a resource at a time.

Constructor

const mutex = new Mutex();

Methods

acquire

Acquires the lock. Returns a release function that must be called to unlock.
acquire(signal?: AbortSignal): Promise<() => void>

Parameters

signal
AbortSignal
Optional AbortSignal to cancel waiting for the lock.

Returns

A promise that resolves with a release function. Call the release function to unlock the mutex.

Throws

  • AbortError if the signal is aborted while waiting

runExclusive

Acquires the lock, runs the function exclusively, and automatically releases the lock.
runExclusive<T>(fn: () => Promise<T>, signal?: AbortSignal): Promise<T>

Parameters

fn
() => Promise<T>
required
The function to run exclusively.
signal
AbortSignal
Optional AbortSignal to cancel waiting for the lock.

Returns

A promise that resolves with the result of the function.

Examples

Basic usage with acquire/release

import { Mutex } from '@temelj/async';

const mutex = new Mutex();
let sharedResource = 0;

async function incrementSafely() {
  const release = await mutex.acquire();
  try {
    // Critical section
    const current = sharedResource;
    await delay(10);
    sharedResource = current + 1;
  } finally {
    release();
  }
}

// Safe concurrent execution
await Promise.all([
  incrementSafely(),
  incrementSafely(),
  incrementSafely(),
]);

console.log(sharedResource); // 3

Using runExclusive

import { Mutex } from '@temelj/async';

const mutex = new Mutex();
let balance = 100;

async function withdraw(amount: number) {
  return await mutex.runExclusive(async () => {
    if (balance >= amount) {
      await delay(10); // Simulate async operation
      balance -= amount;
      return true;
    }
    return false;
  });
}

// Prevents race conditions
const results = await Promise.all([
  withdraw(60),
  withdraw(60),
]);

console.log(results); // [true, false]
console.log(balance); // 40

Database connection pool

import { Mutex } from '@temelj/async';

class Database {
  private mutex = new Mutex();
  private connection: Connection | null = null;

  async query(sql: string) {
    return await this.mutex.runExclusive(async () => {
      if (!this.connection) {
        this.connection = await createConnection();
      }
      return await this.connection.execute(sql);
    });
  }
}

With abort signal

import { Mutex } from '@temelj/async';

const mutex = new Mutex();
const controller = new AbortController();

try {
  const result = await mutex.runExclusive(
    async () => {
      return await longRunningTask();
    },
    controller.signal
  );
} catch (error) {
  if (error instanceof AbortError) {
    console.log('Lock acquisition cancelled');
  }
}

// Cancel if taking too long
setTimeout(() => controller.abort(), 5000);

File writing coordination

import { Mutex } from '@temelj/async';
import { writeFile } from 'fs/promises';

const fileMutex = new Mutex();

async function appendToLog(message: string) {
  await fileMutex.runExclusive(async () => {
    const existing = await readFile('log.txt', 'utf-8');
    await writeFile('log.txt', existing + message + '\n');
  });
}

// Safe concurrent logging
await Promise.all([
  appendToLog('Event 1'),
  appendToLog('Event 2'),
  appendToLog('Event 3'),
]);

Build docs developers (and LLMs) love