乐闻世界logo
搜索文章和话题

What is Background Sync in Service Worker and how to use it?

3月6日 22:01

Service Worker Background Sync Explained

Background Sync is an important feature of Service Worker that allows delayed tasks to be automatically executed after network recovery, particularly useful for data synchronization in offline scenarios.

Core Concept

What problem does Background Sync solve:

  • Users submit forms or perform actions while offline
  • Automatically sync data to server after network recovery
  • No need for users to manually retry operations

Use Cases

  1. Form Submission: Save forms offline, submit automatically when online
  2. Message Sending: Save messages offline, send automatically when online
  3. Data Synchronization: Sync offline operations data when online
  4. Log Upload: Batch upload offline logs after network recovery

Implementation Steps

1. Register Sync Event

javascript
// main.js async function registerBackgroundSync(tag) { const registration = await navigator.serviceWorker.ready; try { await registration.sync.register(tag); console.log(`Background sync registered: ${tag}`); } catch (error) { console.error('Background sync registration failed:', error); } } // Usage example async function submitForm(data) { // Save to local database first await saveToIndexedDB('pending-forms', data); // Register background sync await registerBackgroundSync('sync-forms'); // If online, try to send immediately if (navigator.onLine) { try { await sendToServer(data); await removeFromIndexedDB('pending-forms', data.id); } catch (error) { console.log('Send failed, will retry after network recovery'); } } }

2. Service Worker Handles Sync

javascript
// sw.js // Listen for sync events self.addEventListener('sync', event => { console.log('Background sync triggered:', event.tag); if (event.tag === 'sync-forms') { event.waitUntil(syncPendingForms()); } else if (event.tag === 'sync-messages') { event.waitUntil(syncPendingMessages()); } }); // Sync pending forms async function syncPendingForms() { const pendingForms = await getFromIndexedDB('pending-forms'); for (const form of pendingForms) { try { await fetch('/api/submit-form', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(form) }); // Sync successful, remove from pending list await removeFromIndexedDB('pending-forms', form.id); // Notify user self.registration.showNotification('Sync Successful', { body: 'Your form has been successfully submitted', icon: '/icons/success.png' }); } catch (error) { console.error('Form sync failed:', error); // Failure will automatically retry throw error; } } } // Sync pending messages async function syncPendingMessages() { const messages = await getFromIndexedDB('pending-messages'); const syncPromises = messages.map(async message => { try { await fetch('/api/send-message', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(message) }); await removeFromIndexedDB('pending-messages', message.id); } catch (error) { console.error('Message sync failed:', error); throw error; } }); return Promise.all(syncPromises); }

3. IndexedDB Helper Functions

javascript
// db.js - IndexedDB operation wrapper const DB_NAME = 'background-sync-db'; const DB_VERSION = 1; function openDB() { return new Promise((resolve, reject) => { const request = indexedDB.open(DB_NAME, DB_VERSION); request.onerror = () => reject(request.error); request.onsuccess = () => resolve(request.result); request.onupgradeneeded = event => { const db = event.target.result; // Create object stores if (!db.objectStoreNames.contains('pending-forms')) { db.createObjectStore('pending-forms', { keyPath: 'id' }); } if (!db.objectStoreNames.contains('pending-messages')) { db.createObjectStore('pending-messages', { keyPath: 'id' }); } }; }); } async function saveToIndexedDB(storeName, data) { const db = await openDB(); return new Promise((resolve, reject) => { const transaction = db.transaction(storeName, 'readwrite'); const store = transaction.objectStore(storeName); const request = store.put(data); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); } async function getFromIndexedDB(storeName) { const db = await openDB(); return new Promise((resolve, reject) => { const transaction = db.transaction(storeName, 'readonly'); const store = transaction.objectStore(storeName); const request = store.getAll(); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); } async function removeFromIndexedDB(storeName, id) { const db = await openDB(); return new Promise((resolve, reject) => { const transaction = db.transaction(storeName, 'readwrite'); const store = transaction.objectStore(storeName); const request = store.delete(id); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); }

Complete Example: Offline Form Submission

javascript
// form-handler.js class OfflineFormHandler { constructor(formId) { this.form = document.getElementById(formId); this.setupEventListeners(); } setupEventListeners() { this.form.addEventListener('submit', async (e) => { e.preventDefault(); await this.handleSubmit(); }); } async handleSubmit() { const formData = new FormData(this.form); const data = { id: Date.now().toString(), timestamp: Date.now(), ...Object.fromEntries(formData) }; // Show submitting status this.showStatus('Saving...'); try { // Save locally await saveToIndexedDB('pending-forms', data); // Register background sync await this.registerSync('sync-forms'); // Try to send immediately if (navigator.onLine) { await this.sendImmediately(data); } else { this.showStatus('Saved offline, will sync when online'); } this.form.reset(); } catch (error) { this.showStatus('Save failed: ' + error.message); } } async registerSync(tag) { const registration = await navigator.serviceWorker.ready; if ('sync' in registration) { await registration.sync.register(tag); } else { // Fallback: send immediately or notify user console.log('Browser does not support background sync'); } } async sendImmediately(data) { try { await fetch('/api/submit-form', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); await removeFromIndexedDB('pending-forms', data.id); this.showStatus('Submitted successfully!'); } catch (error) { this.showStatus('Submit failed, will retry in background'); } } showStatus(message) { const statusEl = document.getElementById('form-status'); statusEl.textContent = message; setTimeout(() => { statusEl.textContent = ''; }, 3000); } } // Initialize const formHandler = new OfflineFormHandler('my-form');

Periodic Background Sync

javascript
// Request periodic sync permission async function requestPeriodicSync() { const registration = await navigator.serviceWorker.ready; try { await registration.periodicSync.register('daily-sync', { minInterval: 24 * 60 * 60 * 1000 // At least 24 hours }); console.log('Periodic sync registered'); } catch (error) { console.error('Periodic sync registration failed:', error); } } // sw.js handles periodic sync self.addEventListener('periodicsync', event => { if (event.tag === 'daily-sync') { event.waitUntil(performDailySync()); } }); async function performDailySync() { // Execute daily sync tasks const data = await fetch('/api/daily-data').then(r => r.json()); await caches.open('daily-cache').then(cache => { cache.put('/daily-data', new Response(JSON.stringify(data))); }); }

Browser Compatibility

FeatureChromeFirefoxSafariEdge
Background Sync
Periodic Sync

Best Practices

  1. Data Persistence: Use IndexedDB to store pending sync data
  2. Error Handling: Throw errors on sync failure to trigger retry
  3. Idempotent Design: Ensure repeated execution doesn't cause side effects
  4. User Feedback: Notify users after sync completion
  5. Fallback Solution: Provide alternative for unsupported browsers
javascript
// Fallback example async function syncWithFallback() { const registration = await navigator.serviceWorker.ready; if ('sync' in registration) { // Use background sync await registration.sync.register('sync-data'); } else { // Fallback: try immediately or notify user if (navigator.onLine) { await syncData(); } else { alert('You are offline, please retry when online'); } } }
标签:Service Worker