JavaScript Web Locks Table

PieceWhat it doesField note
navigator.locks.request(name, cb)Hold a cross-tab lockThe ONLY cross-tab coordination primitive in the platform
exclusive (default)One holder at a timeMigration and single-writer patterns
{ mode: 'shared' }Many readersShared vs exclusive = the read/write lock you know
ifAvailable: trueNo waitingLock OR null - the try-acquire for 'am I the only tab?'
steal: trueBreak a stale holderFor dead-tab recovery - the old holder's promise never resolves
Lock auto-releaseOn callback end or tab deathTab crash releases the lock - no stale locks in storage
held.getHolderSnapshotInspect holdersDebug which tab holds what
vs localStorage flagAtomic vs raceA flag can be double-claimed; a lock cannot - that is the point
Reference: the MDN Web Locks reference. Web Locks is the platform's only cross-tab coordination primitive: navigator.locks.request('migration', callback) holds a named lock for the callback's duration - one tab runs the migration, other tabs wait or see ifAvailable: true return null. The two properties that kill the localStorage-flag patterns: locks are ATOMIC (a flag can be double-claimed in a race; a lock cannot) and SELF-RELEASING (tab crash or close releases automatically - no stale flags stuck in storage). Bottom line: exclusive for single-writer migrations, shared mode for multi-reader caches, steal: true for dead-holder recovery. Related tools: localStorage table (the flag patterns locks replace), service worker table (the other multi-tab actor), and event loop table (await the lock, then act).

Web Locks is the platform's only cross-tab coordination primitive: navigator.locks.request('migration', callback) holds a NAMED lock for the callback's duration - one tab runs the migration, other tabs wait, or check with ifAvailable and proceed differently.

Bottom line: two properties kill the localStorage-flag patterns: locks are ATOMIC (a flag can be double-claimed in a race between two tabs; a lock cannot - the platform serializes the grant), and locks are SELF-RELEASING (tab crash or close releases automatically - no stale flags stuck in storage demanding manual cleanup).

The honest part: locks coordinate TABS on one device on one origin - they are not a distributed lock service, have no network semantics, and vanish with the browser process. Cross-device coordination still needs the server.

How to use

  1. Single-writer migration: navigator.locks.request('db-migration', async () => { runMigrations(); }) - exactly one tab migrates; the rest simply wait for it to finish.
  2. Try-acquire without waiting: await navigator.locks.request('leader', { ifAvailable: true }, async lock => { if (!lock) return; beLeader(); }) - 'am I the only tab?' in one call.
  3. Recover from dead holders: request with { steal: true, ifAvailable: false } - breaks a stale lock whose holder died mid-task, for long-running-job recovery.

Frequently asked questions

What did teams do before Web Locks, and why were the patterns fragile?

localStorage flags and BroadcastChannel elections. The flag pattern: tab A writes 'migration-running = true', other tabs check and skip. It fails on timing (two tabs read false before either writes - both migrate), on crashes (the flag stays true forever after a tab dies mid-migration, blocking all future runs until someone clears it manually), and on clarity (the flag is a hint; nothing enforces it). Web Locks fixes all three structurally: the grant is atomic (the platform serializes requests), release is automatic on tab death, and waiting tabs are genuinely BLOCKED, not politely-informed. The pattern wasn't badly written - it was built on a storage primitive that cannot promise coordination.

How do exclusive and shared modes map to real coordination needs?

Classic read/write locking. Exclusive (the default) means one holder: migrations, single-leader election, 'only this tab writes the cache'. Shared mode means unlimited co-holders but blocks exclusive requests: many tabs reading and validating a shared resource while an updater waits for them all to finish. The semantics compose like database locks - a shared holder arrives while an exclusive waits and the exclusive waits for ALL shared holders to release. The subtlety worth knowing: the mode is per-REQUEST, so the same named lock can be requested shared by five tabs and exclusive by a sixth, and the platform arbitrates the ordering.

When do ifAvailable and steal earn their keep?

ifAvailable is the non-blocking ask: you get the lock OR null immediately - the 'am I the leader?' check, the skip-if-busy background job, the UI that shows 'another tab is editing' instead of queueing. steal: true is the recovery tool: it TAKES the lock even if held, breaking a dead or stuck holder - the long-running job whose tab crashed and whose lock would otherwise block every retry forever (the pre-locks stale-flag problem, solved at the API level). Steal is deliberately violent - the previous holder's callback stays waiting while it believes it holds the lock - so it belongs in recovery paths with explicit human or heartbeat triggers, not in routine acquisition.

What are the boundaries of Web Locks as a coordination tool?

One device, one browser, one origin, one process lifetime. Locks live in the browser's memory: they coordinate tabs of the same origin on the same machine - not across devices, not across browsers, not after the browser restarts. They are also not QUEUES: there is no fairness guarantee about which waiting tab gets the lock next, and no message-passing - the lock says 'your turn', not 'here is the data'. Cross-device coordination, durable job claims and ordered work distribution remain server-side concerns (database transactions, queues). Within its boundary - multi-tab single-device coordination like migrations, leader election and cache rebuild dedupe - it is the only primitive the platform offers, and it does that one job atomically.

Related tools