How Background Sync API Defers Offline Actions
The Background Sync API is a web standard that allows web applications to defer network operations until the user has a stable internet connection. By delegating pending tasks—such as chat messages, form submissions, or analytics logs—to a Service Worker, the API ensures that user actions are reliably executed in the background, even if the user navigates away from the page or closes the browser tab before connectivity is restored.
The Core Architecture
The Background Sync API relies on three main components working together:
- Client-Side Application: Detects offline conditions or failed requests and registers a sync tag.
- IndexedDB: Stores the serialized payload (e.g., request body, headers, URLs) locally while offline.
- Service Worker: Listens for the
syncevent triggered by the browser once connectivity is restored and executes the deferred fetch requests.
Step-by-Step Implementation Flow
1. Storing Offline Data
When a user attempts a network action while offline, the application catches the failure and writes the request data into IndexedDB to preserve it across sessions.
// Storing data to IndexedDB when offline
async function saveRequestLocally(data) {
const db = await openDatabase(); // Utility function to open IndexedDB
const tx = db.transaction('offline-queue', 'readwrite');
await tx.objectStore('offline-queue').add(data);
}2. Registering a Sync Event
Once the data is saved, the client requests a sync event through the active Service Worker registration using a descriptive tag.
async function registerBackgroundSync() {
if ('serviceWorker' in navigator && 'SyncManager' in window) {
const registration = await navigator.serviceWorker.ready;
try {
await registration.sync.register('sync-deferred-posts');
console.log('Background sync registered.');
} catch (error) {
console.error('Background sync registration failed:', error);
}
} else {
// Fallback: Attempt immediate sync when navigator.onLine fires
window.addEventListener('online', sendFallbackRequest);
}
}3. Handling the Sync Event in the Service Worker
Inside the Service Worker script (sw.js), listen for the
sync event. The browser automatically fires this event as
soon as it detects a network connection.
// sw.js (Service Worker)
self.addEventListener('sync', (event) => {
if (event.tag === 'sync-deferred-posts') {
event.waitUntil(processDeferredRequests());
}
});
async function processDeferredRequests() {
const db = await openDatabase();
const queue = await db.getAll('offline-queue');
for (const item of queue) {
try {
const response = await fetch('/api/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(item.data)
});
if (response.ok) {
// Remove processed item from IndexedDB
await db.delete('offline-queue', item.id);
}
} catch (error) {
// If the fetch fails, throw error to signal retry
throw error;
}
}
}Key Behaviors and Lifecycle Rules
event.waitUntil()Guarantee: Wrapping the network operation inevent.waitUntil()prevents the browser from terminating the Service Worker until the asynchronous task resolves or rejects.- Automatic Retries: If the promise passed to
event.waitUntil()rejects (e.g., if the network drops again during sync), the browser schedules another attempt based on an internal exponential backoff strategy. - Independent Execution: The sync process runs entirely in the background. The user does not need to keep the original web page open for the task to finish successfully.