Skip to content

Background Sync

Background Sync Mechanics

Background sync enables Progressive Web Apps (PWAs) to defer critical tasks—such as data uploads or state updates—until the user regains network connectivity. This ensures reliability even during intermittent or complete offline periods. The Background Sync API, combined with the Fetch API, allows developers to queue tasks and execute them reliably when the browser is back online. This section explores how to implement background sync, handle multiple sync events, and ensure task reliability.


## Overview of the Background Sync API

The Background Sync API provides a way to schedule tasks that run in the background, even if the user navigates away from the app or closes the browser. Key components include:

  • SyncManager: Manages sync tasks, allowing registration of sync events with unique tags.
  • SyncEvent: Represents a sync request, providing access to the task's tag and metadata.
  • event.waitUntil(): Ensures the task completes before the browser considers it successful.

Example:

// Register a sync task with a unique tag
self.registration.sync.register('critical-task');

This API is particularly useful for tasks like form submissions or data backups, ensuring they are not lost during network outages.


## Integrating with the Fetch API

Background sync is often used in conjunction with the Fetch API to defer network requests. When a fetch request is made with the sync option, the browser queues the task and executes it when connectivity returns.

Example:

// Fetch data with background sync
fetch('/api/data', {
  method: 'POST',
  body: JSON.stringify({ key: 'value' }),
  keepalive: true, // Ensures the request is queued for sync
});

If the network is unavailable during the fetch, the task is stored and executed later. The service worker can then process the request using event.waitUntil() to ensure completion.


## Handling Multiple Sync Events

Sync tasks can be queued and processed in order. Use the onSync event listener in the service worker to handle registered tasks:

self.addEventListener('sync', (event) => {
  if (event.tag === 'critical-task') {
    event.waitUntil(handleCriticalTask());
  }
});

async function handleCriticalTask() {
  // Perform the task (e.g., save data to IndexedDB)
  // Return a Promise to signal success or failure
}

Each sync event is processed sequentially, ensuring that tasks are executed in the order they were registered. This is critical for maintaining data consistency.


## Ensuring Reliability with Retry Logic

Background sync tasks are not guaranteed to succeed. Implement retry logic to handle failures, such as network errors or server downtime.

Example:

async function retrySync(task, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      await task();
      return true; // Success
    } catch (error)  
      console.error(`Sync attempt ${i + 1} failed:`, error);
      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); // Exponential backoff
    }
  }
  return false; // All retries failed
}

Combine this with the SyncManager to track task status and avoid redundant processing.


## Best Practices

  1. Use sync for critical, idempotent tasks (e.g., data uploads) and avoid non-critical operations.
  2. Pair with IndexedDB to persist data locally before syncing.
  3. Monitor sync success/failure using event.waitUntil() and logging.
  4. Limit sync frequency to avoid overwhelming servers or draining battery.

Key takeaways

  • Background sync defers tasks to when the user is online, ensuring reliability during outages.
  • Use SyncManager.register() to queue tasks and onSync to process them in the service worker.
  • Combine with IndexedDB for local data persistence and retry logic for robustness.
  • Prioritize idempotent operations and avoid overusing sync to maintain performance.
  • Always handle sync failures gracefully and provide user feedback when necessary.