Concept in Service Worker
Background sync, periodic sync, and Web Locks
Published
Background Sync asks a service worker to retry deferred work when the user agent considers the network suitable. Periodic Background Sync asks a service worker to refresh data at browser-selected intervals, while Web Locks coordinates live tabs and workers that contend for an application-defined resource.
The support split changes the fallback. BCD api.SyncManager records Background Sync from Chrome 49 and mirrors that support for Edge, with no Firefox or Safari support. BCD api.PeriodicSyncManager records periodic sync from Chrome 80 and mirrors that support for Edge, with no Firefox or Safari support. BCD api.LockManager records Web Locks from Chrome 69, mirrors that support for Edge, and records support from Firefox 96 and Safari 15.4.
How it works
Section titled “How it works”Each mechanism answers a different scheduling question. Background Sync registers a tag through ServiceWorkerRegistration.sync; the user agent later delivers a sync event to the active service worker. Periodic Background Sync registers a tag and minInterval through ServiceWorkerRegistration.periodicSync; the specification leaves the actual firing time to the user agent. Web Locks calls navigator.locks.request() and runs a callback after the named lock is granted. The lock lasts until that callback’s promise settles.
| Mechanism | Trigger | Execution context | Useful fallback |
|---|---|---|---|
| Background Sync | The user agent finds network conditions suitable after sync.register() |
Service worker sync event |
Retry from the next page load |
| Periodic Background Sync | The user agent chooses a time no more frequent than the registered minInterval |
Service worker periodicsync event |
Refresh when a page opens |
| Web Locks | A named lock becomes available after navigator.locks.request() |
The requesting window or worker | Run without cross-context coordination or use an application-level lease |
Background Sync fits an operation that has already failed or was deliberately deferred, such as sending a queued form. The Background Sync specification defines one-off registration through SyncManager. A separate Periodic Background Sync specification defines periodic registration through PeriodicSyncManager. Registering work does not make the request itself durable: store the request data before registering the tag, then remove it after the service worker completes the operation.
Periodic Background Sync fits data that becomes stale without a user action. Chrome’s guide says synchronization timing is not controlled by the developer and considers engagement, previously used networks, power, and connectivity. minInterval is a lower bound, not a timer.
Web Locks does not schedule a service worker or wait for connectivity. The Web Locks specification grants exclusive locks one at a time and allows multiple shared locks on one name. The callback controls the lifetime, so use it around the smallest asynchronous operation that must not overlap. A closed page cannot use a Web Lock as a durable retry queue.
| Decision | Choose | Reason |
|---|---|---|
| Retry a stored mutation after connectivity returns | Background Sync | The sync event is designed for deferred one-off work |
| Refresh cached content without an open page | Periodic Background Sync | The periodicsync event is designed for browser-scheduled refreshes |
| Prevent two open contexts from updating the same record together | Web Locks | An exclusive lock serializes callbacks that request the same name |
| Support Firefox and Safari with the same coordination primitive | Web Locks | Their recorded support includes LockManager, not either sync manager |
| Require an exact wall-clock deadline | None of these | The sync specifications leave delivery to the user agent, and a lock only governs contention |
Examples
Section titled “Examples”Store durable work first, and keep runtime coordination separate from delivery scheduling. Both examples include a page-load path so engines without a sync API still make progress.
Retrying a queued request without Background Sync
Section titled “Retrying a queued request without Background Sync”The page writes the operation to IndexedDB before asking for a one-off sync. If SyncManager is absent or registration rejects, it calls the same queue processor while the page remains open.
async function queueMessage(message) { await outbox.put(message); const registration = await navigator.serviceWorker.ready; if (!('sync' in registration)) { return flushOutbox(); } try { await registration.sync.register('send-outbox'); } catch (error) { await flushOutbox(); reportRetryFallback(error); }}The service worker handles the same stored queue. Passing the promise to waitUntil() lets the user agent extend the event while flushOutbox() runs, as required by the Background Sync event model.
self.addEventListener('sync', (event) => { if (event.tag !== 'send-outbox') return; event.waitUntil(flushOutbox());});
self.addEventListener('message', (event) => { if (event.data?.type !== 'flush-outbox') return; event.waitUntil(flushOutbox());});Refreshing once while periodic sync is unavailable
Section titled “Refreshing once while periodic sync is unavailable”The page registers periodic refresh only after the API and permission checks pass. The failure path refreshes immediately and leaves the next page load responsible for another attempt.
async function scheduleCatalogRefresh() { const registration = await navigator.serviceWorker.ready; if (!('periodicSync' in registration)) { return refreshCatalogNow(); } const status = await navigator.permissions.query({ name: 'periodic-background-sync', }); if (status.state !== 'granted') return refreshCatalogNow(); try { await registration.periodicSync.register('catalog', { minInterval: 24 * 60 * 60 * 1000, }); } catch (error) { await refreshCatalogNow(); reportRefreshFallback(error); }}When several open contexts can call refreshCatalogNow(), wrap that function with a Web Lock. This changes overlap between live contexts; it does not replace the stored queue or cause a service worker event.
async function refreshCatalogOnce() { if (!('locks' in navigator)) return refreshCatalogNow(); return navigator.locks.request('catalog-refresh', { ifAvailable: true, }, async (lock) => { if (!lock) return { status: 'already-running' }; await refreshCatalogNow(); return { status: 'refreshed' }; });}The Background Sync compatibility data and Web Locks compatibility data show why both feature tests remain necessary.
See also
Section titled “See also”- Background Synchronization (wicg.github.io)
- Web Periodic Background Synchronization (wicg.github.io)
- Web Locks API (w3c.github.io)
- Background Sync
- Periodic Background Sync
- Web Locks API
Specifications
| Specification | Status |
|---|---|
| Background Sync | WICG draft |
- Legend
- Yes
- Partial
- Flag
- No
- Unknown
| Browser / Platform | Support | Versions | Confidence | Source | Notes |
|---|---|---|---|---|---|
| Chrome (Desktop)# | Yes | 49 | high | source | — |
| Chrome (Android)# | Yes | 49 | high | source | 1 |
| Edge (Desktop)# | Yes | 79 | high | source | 2 |
| Firefox (Desktop)# | No | — | high | source | 3 |
| Firefox (Android)# | No | — | high | source | 45 |
| Safari (macOS)# | No | — | high | source | 6 |
| Safari (iOS)# | No | — | high | source | 78 |
| Samsung Internet# | Yes | 5.0 | high | source | 9 |
| WebView (Android)# | No | — | high | source | 10 |
- Derived by browser-compat-data mirroring from Chrome.
- Derived by browser-compat-data mirroring from Chrome.
- No Firefox support is recorded in browser-compat-data.
- No Firefox for Android support is recorded in browser-compat-data.
- Derived by browser-compat-data mirroring from Firefox.
- Implementation tracking: https://webkit.org/b/182565.
- Implementation tracking: https://webkit.org/b/182565.
- Derived by browser-compat-data mirroring from Safari.
- Derived by browser-compat-data mirroring from Chrome Android.
- Implementation tracking: https://crbug.com/40449796.