Build1 distinct publisher2 min readPublished
The browser has shipped an origin-scoped mutex since March 2022. The lock lives exactly as long as your callback's promise, so the token rotation runs once no matter how many tabs notice the expiry.
The Engineer · Build desk

build
The exit ping that never left: fetch in beforeunload loses to the browser's unload policy1 distinct publisher
build
Three manual interventions in a month, and every guard was working as designed1 distinct publisher
build
Six MariaDB versions, one real difference: the only reason to leave 10.6 is the July 2026 clock1 distinct publisher
build
Force the tool call, then hand Lightsail a long-lived key1 distinct publisher
Compiled by The EngineerSomething wrong?How this is made
The re-check inside the callback is the part implementations skip, and it is the part that does the deduplication. Three tabs queue on the name `token-refresh`. The first does the network round trip and writes the result. The second and third take the lock after it, see a token that is no longer expired, and return without a fetch [7]. Take the `isExpired` guard out and the lock still works exactly as specified: you get three sequential refreshes instead of three concurrent ones, queued politely, each rotating the token out from under the last.
The arithmetic is worth stating because it is the whole return on the change. Three tabs queue, one fetch runs, and the other two requests never leave the browser [1]. That number transfers only if all three documents sit on the same origin and read the same token store, because the lock namespace is the origin and the guarantee covers code this browser can observe [2]. Auth split across two origins, or a phone and a desktop tab calling the same refresh endpoint, get no coordination from this at all.
There is a reason the racy version survives review. Reproducing the failure needs at least two documents noticing expiry in the same tick [1], and a single-tab development loop produces zero contention [3]. The bug ships because the test never runs it.
`signal` takes an AbortSignal, so a tab can give up after 200ms rather than queue behind a slow holder [11]. That is the second option I would reach for, after the re-check, because a queued request has no timeout of its own and a tab parked on another monitor since yesterday is a plausible holder [1].
Deadlock stays your problem. A tab holding `a` while waiting on `b`, against a tab doing the reverse, waits forever [16]. Two lock names in one code path is where that starts, so acquire them in a fixed order or collapse them into one name.
Context for the opinion, since this is a tradeoff and not a rule: I would use this for token refresh and for deciding which tab owns the WebSocket [9], and I would not treat it as a queue. It is a handful of lines, and it removes a class of bug that a flag in storage cannot remove, because the atomic compare-and-set that would make that flag correct does not exist in `localStorage` [2].
Ranked by verification strength, evidence, and original report placement.
The Web Locks API has shipped in browsers since March 2022, is Baseline Widely available, and almost nobody reaches for it.
Three tabs open on the same dashboard can all notice an expired access token at roughly the same instant and all fire a refresh request; two get back a rotated refresh token that the third has already invalidated, and the user is sent to a login screen. One of the tabs in the example has been parked on another monitor since yesterday.
localStorage has no atomic compare-and-set, so two tabs can read 'no refresh in progress' in the same tick and both write 'refresh in progress'.
The usual fix is a pile of localStorage flags with timestamps, a BroadcastChannel message, and a comment saying '// TODO: this is racy'.
navigator.locks.request takes a name and a callback, and the browser guarantees that no other code on the same origin, in any tab, iframe or worker, runs inside a lock with that name at the same time.
The lock is held for exactly as long as the callback's promise is pending and is released when the callback returns or throws; there is no unlock() to forget and no leaked lock if the fetch inside rejects.
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 29, 2026
Follow any of these and your For You feed starts watching them — no settings page required.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Checkable, but single-sourced
Nearly every assertion here is the kind you can falsify in a browser console before finishing your coffee: the lock releases when the callback's promise settles, ifAvailable hands you null, signal alongside steal rejects with NotSupportedError. That specificity is what holds the story up, because the sourcing does not — one dev.to post, one author, MDN gestured at rather than quoted. The weakest joint is the ecosystem claim, which has no data under it at all.
Available everywhere, used by no one we can name
Not one application appears in this reporting as having shipped a named lock. Baseline Widely available tells us the capability is present in browsers; 'almost nobody reaches for it' tells us what one developer believes about his peers. Neither is a deployment, and we decline to convert a support matrix into uptake.
Undersold rather than oversold
The headline promise — three tabs, one request — is just the documented mechanism plus an expiry check, and dev.to spends its closing third on things that go wrong: deadlock you still own, a stolen lock the victim never learns about, no durability across a reload. Pitches do not usually volunteer that list. The single flourish running ahead of its evidence is the swipe at how few developers use the API.
Nothing for sale
No product, no vendor, no benchmark to win — a free browser primitive with a closing pointer to MDN's LockManager reference. What remains is the ordinary economy of a developer blog, where 'almost nobody reaches for it' earns clicks that 'this is well documented' would not. That pressure shows up in the framing, not in the technical content.
One voice on verifiable ground
Single publisher, no corroboration, and an adoption question we had to leave open — that would normally sink confidence further. It does not here, because API behaviour is a claim class that survives thin sourcing: a reader with a second tab settles it. We are confident about the mechanism and deliberately not confident about how widely it is used.