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.