Start · Sprachen · JavaScript · Referenz · LockManager

LockManager

Schnittstelle

Die <code>LockManager</code>-Schnittstelle der Web Locks API fordert mit <code>request()</code> benannte Sperren an und liefert mit <code>query()</code> den aktuellen Sperrzustand.

seit JavaScript Baseline 2022: Chrome 69, Edge 79, Firefox 96, Saf Kategorie: webapi

Signatur

interface LockManager

Beschreibung

LockManager ist der Einstiegspunkt der Web Locks API. Eine Instanz erzeugt man nicht selbst. Man erreicht sie über navigator.locks im Fenster-Kontext oder in Workern (self.navigator.locks). Mit der Schnittstelle stimmen sich mehrere Tabs, iframes und Worker desselben Ursprungs (Origin) ab, damit nur einer von ihnen gleichzeitig auf eine gemeinsame Ressource zugreift. Typische Ressourcen sind eine IndexedDB-Synchronisation, ein Token-Refresh oder eine WebSocket-Verbindung, die nur einmal existieren soll.

Die Schnittstelle hat zwei Methoden:

  • request(name, [options], callback) fordert eine Sperre mit einem frei gewählten Namen an. Sobald die Sperre erteilt ist, wird callback mit einem Lock-Objekt aufgerufen. Die Sperre bleibt bestehen, bis der Callback fertig ist. Gibt der Callback ein Promise zurück, wird die Sperre erst freigegeben, wenn dieses Promise erfüllt oder abgelehnt ist.
  • query() liefert eine Momentaufnahme: welche Sperren gerade gehalten werden (held) und welche noch warten (pending). Das ist vor allem für Fehlersuche und Monitoring nützlich.

Es gibt zwei Sperrmodi: "exclusive" (Standard, immer nur ein Halter) und "shared" (beliebig viele gleichzeitige Halter, z. B. für Lesezugriffe). Solange eine exklusive Sperre gehalten wird, erhält niemand eine geteilte Sperre, und umgekehrt. Anfragen werden der Reihe nach abgearbeitet, in der sie eingegangen sind.

Die Parameter unten beziehen sich auf die zentrale Methode LockManager.prototype.request().

Parameter

Name Typ Default Beschreibung
$name Pflicht string Frei wählbarer Name der Sperre, gültig innerhalb des Ursprungs. Namen, die mit - beginnen, sind reserviert und lösen einen NotSupportedError aus.
$options object {} Optionales Konfigurationsobjekt mit den unten aufgeführten Feldern.
$options.mode "exclusive" | "shared" "exclusive" Sperrmodus: exklusiv (ein Halter) oder geteilt (mehrere Halter gleichzeitig).
$options.ifAvailable boolean false Bei true wird die Sperre nur erteilt, wenn sie sofort frei ist. Andernfalls wird der Callback sofort mit null aufgerufen, statt zu warten.
$options.steal boolean false Bei true werden bestehende Sperren gleichen Namens freigegeben und die Anfrage sofort erfüllt. Die Promises der bisherigen Halter werden mit AbortError abgelehnt. Nur zusammen mit mode: "exclusive" erlaubt, nicht mit ifAvailable oder signal.
$options.signal AbortSignal Damit lässt sich eine noch wartende Anfrage abbrechen, etwa per Zeitlimit. Das Promise wird dann mit AbortError abgelehnt. Eine bereits erteilte Sperre wird dadurch nicht freigegeben.
$callback Pflicht (lock: Lock | null) => any | Promise<any> Wird aufgerufen, sobald die Sperre erteilt ist (bzw. mit null bei ifAvailable, wenn die Sperre belegt ist). Die Sperre gilt, bis der Callback bzw. sein zurückgegebenes Promise abgeschlossen ist.

Rückgabewert

Typ
Promise<any> (request) | Promise<LockManagerSnapshot> (query)
Beschreibung
request() gibt ein Promise zurück. Es wird mit dem Rückgabewert des Callbacks erfüllt, sobald die Sperre wieder freigegeben ist. query() liefert ein Promise auf ein Objekt { held: LockInfo[], pending: LockInfo[] }. Jedes LockInfo enthält name, mode und clientId.

Beispiele

Exklusive Sperre für einen Token-Refresh über mehrere Tabs

const refreshToken = async () => {
  // Nur ein Tab gleichzeitig darf den Token erneuern
  return navigator.locks.request('auth-refresh', async (lock) => {
    console.log(`Sperre erhalten: ${lock.name} (${lock.mode})`);
    const res = await fetch('/api/token/refresh', { method: 'POST' });
    const { token } = await res.json();
    localStorage.setItem('token', token);
    return token; // wird zum Wert des äußeren Promise
  });
};

const token = await refreshToken();
console.log('Neuer Token gespeichert');
Sperre erhalten: auth-refresh (exclusive) Neuer Token gespeichert

Nur ausführen, wenn frei (ifAvailable) – z. B. ein einziger Sync-Leader

const startSyncIfLeader = () =>
  navigator.locks.request('sync-leader', { ifAvailable: true }, async (lock) => {
    if (!lock) {
      console.log('Ein anderer Tab synchronisiert bereits.');
      return;
    }
    console.log('Dieser Tab ist jetzt Leader.');
    // Sperre halten, bis der Tab geschlossen wird
    await new Promise(() => {});
  });

startSyncIfLeader();
Dieser Tab ist jetzt Leader. (im ersten Tab) Ein anderer Tab synchronisiert bereits. (in weiteren Tabs)

Wartende Anfrage mit Zeitlimit abbrechen

try {
  await navigator.locks.request(
    'db-migration',
    { signal: AbortSignal.timeout(3000) },
    async () => {
      await runMigration();
    }
  );
} catch (err) {
  if (err.name === 'TimeoutError' || err.name === 'AbortError') {
    console.warn('Sperre nicht innerhalb von 3 s erhalten.');
  } else {
    throw err;
  }
}

async function runMigration() {
  console.log('Migration läuft …');
}
Migration läuft … (oder: Sperre nicht innerhalb von 3 s erhalten.)

Sperrzustand mit query() und geteilte Sperren

// Zwei Leser gleichzeitig mit geteilter Sperre
const read = (id) =>
  navigator.locks.request('cache', { mode: 'shared' }, async () => {
    await new Promise((r) => setTimeout(r, 500));
    return `Leser ${id} fertig`;
  });

const readers = [read(1), read(2)];

const { held, pending } = await navigator.locks.query();
console.log('Gehalten:', held.map((l) => `${l.name}/${l.mode}`));
console.log('Wartend:', pending.length);
console.log(await Promise.all(readers));
Gehalten: ["cache/shared", "cache/shared"] Wartend: 0 ["Leser 1 fertig", "Leser 2 fertig"]