A check is not a reservation.
Reading a value and writing it back are separate operations. Two tabs can both read zero before either writes one. This deterministic, in-memory simulation exposes that same lost-update pattern without changing your storage.
A localStorage flag also needs stale-lock recovery. BroadcastChannel delivers messages, but does not itself grant exclusive access.
One name. One exclusive owner.
Requests for the same exclusive lock wait their turn. The browser holds the lock until the callback’s returned promise settles. Here, three tasks safely update a counter in this page.
Only cooperating callers are synchronized. Use the same lock name around every operation that needs protection.
Let one tab handle the upload.
With ifAvailable: true, a competing request receives null immediately. Start an upload, then try a competing caller—or open this page in another tab at the same origin.
The option is case-sensitive: ifAvailable. This corrects the article’s sample typo. The async callback awaits the entire transfer; cancellation ends the simulated work before releasing the lock.
Shared readers. Exclusive writers.
Shared locks can coexist. An exclusive lock waits for conflicting holders to finish. Add two readers, then a writer, and watch the order. An AbortSignal can cancel a request while it is waiting.
Aborting a request signal does not stop a callback that already owns its lock. The optional steal flag forcibly takes ownership but does not stop the previous owner’s code; it is intentionally omitted from these controls.
See who owns the work.
navigator.locks.query() returns a snapshot of held and pending locks. Start a demo above and refresh to see its lock name, mode, and client identifier.
A snapshot is for observation, not acquisition: its contents can change immediately. Use request() to coordinate work.