JavaScript Background Sync: How to Defer Offline Tasks
Background Sync is a web API that enables web applications to defer actions and network requests until a user has a stable internet connection. By combining Service Workers with the Background Sync API, developers can ensure that user actions—such as sending a chat message, submitting a form, or uploading data—are not lost when connectivity drops. This article explains what the Background Sync API is, how it operates under the hood, and how to implement it in JavaScript to reliably handle offline tasks.
Understanding the Background Sync API
The Background Sync API is built on top of Service Workers. Unlike
standard fetch requests triggered from the main browser
thread, background sync operations run in the background inside a
Service Worker.
When a user performs an action while offline, the application
registers a “sync tag” with the Service Worker. Even if the user
navigates away from the page or closes the browser tab, the browser
retains this sync registration. As soon as the device detects that
internet connectivity has been restored, the browser wakes up the
Service Worker and fires the sync event, allowing the
queued requests to be processed automatically.
How JavaScript Implements Background Sync
Implementing background sync involves a three-step workflow: storing the offline data locally, registering a sync event in the main script, and handling the sync event inside the Service Worker.
1. Storing Offline Data Locally
Since the page might be closed before connectivity returns, any payload (such as form data) must be persisted in an offline-accessible storage mechanism, typically IndexedDB.
// Example: Storing an action in IndexedDB
async function saveTaskForSync(taskData) {
const db = await openDatabase(); // Open your IndexedDB instance
const tx = db.transaction('outbox', 'readwrite');
await tx.objectStore('outbox').add(taskData);
await tx.done;
}2. Registering the Sync Event from the Main Thread
Once the data is saved in IndexedDB, the client registers a sync event using the Service Worker registration.
async function requestSync(tag) {
if ('serviceWorker' in navigator && 'SyncManager' in window) {
const registration = await navigator.serviceWorker.ready;
await registration.sync.register(tag);
} else {
// Fallback for browsers that do not support the Background Sync API
sendDataDirectly();
}
}3. Listening for the Sync Event in the Service Worker
Inside the sw.js (Service Worker file), you listen for
the sync event. When triggered, retrieve the pending items
from IndexedDB and send them to the server.
self.addEventListener('sync', (event) => {
if (event.tag === 'sync-messages') {
event.waitUntil(sendPendingMessages());
}
});
async function sendPendingMessages() {
const db = await openDatabase();
const messages = await db.getAll('outbox');
for (const message of messages) {
try {
const response = await fetch('/api/send-message', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(message)
});
if (response.ok) {
// Remove successfully sent message from IndexedDB
await db.delete('outbox', message.id);
} else {
// Throw an error to signal the sync should be retried later
throw new Error('Server responded with an error');
}
} catch (error) {
// Re-throwing causes the browser to schedule another sync attempt
throw error;
}
}
}Retry Mechanism and Resilience
The event.waitUntil() method is critical to background
sync. It takes a Promise and tells the browser not to terminate the
Service Worker until the operation completes.
If the network drops mid-request or the server returns a failure, the
Promise passed to event.waitUntil() rejects. The browser
recognizes this failure and automatically reschedules the sync event to
retry after a short backoff period.
Fallback Strategies
For browsers that do not support the Background Sync API (such as
Safari and Firefox), applications should implement a fallback mechanism.
The standard approach uses the
window.addEventListener('online', ...) event listener in
the main execution thread to detect restored connectivity and
immediately flush any queued tasks stored in IndexedDB.