Blog

Async Mutex in TypeScript – Implementierung und Tücken

Das Problem

JavaScript ist single-threaded, aber async/await erzeugt interleaving zwischen Tasks. Wenn mehrere asynchrone Operationen auf geteiltem Zustand arbeiten, braucht man einen Mutex.

Eine einfache Implementierung

type Release = () => void;

class AsyncMutex {
  private queue: Array<(release: Release) => void> = [];
  private locked = false;

  acquire(): Promise<Release> {
    return new Promise((resolve) => {
      const tryAcquire = (release: Release) => {
        if (!this.locked) {
          this.locked = true;
          resolve(release);
        } else {
          this.queue.push(tryAcquire);
        }
      };

      const release: Release = () => {
        this.locked = false;
        const next = this.queue.shift();
        if (next) next(release);
      };

      tryAcquire(release);
    });
  }
}

None-Typen mit Symbol

Statt null | undefined lässt sich ein Symbol-basierter None-Typ präziser typisieren:

const None = Symbol('None');
type None = typeof None;

type Option<T> = T | None;

function isNone<T>(value: Option<T>): value is None {
  return value === None;
}

Fazit

Closure-basierte Mutexe sind in TypeScript idiomatisch und typsicher. Symbol-basierte None-Typen vermeiden die Ambiguität zwischen null, undefined und legitimem Fehlerfehlen eines Wertes.