DEV Community

Cover image for Service Workers
Marko Rajević
Marko Rajević

Posted on • Updated on

Service Workers

A Service Worker is a script that runs in the background of a web browser and, they are the answer if you're aiming for:

  • Fast loading (even in unpredictable network conditions)
  • Offline access
  • Real-time push notifications
  • Background sync

The Service Worker can continue to operate even when the web page is closed, thus allowing for extended functionality that enhances the user experience.

They operate on their thread, separate from the main thread.
Service Workers are restricted to running only over HTTPS for security reasons.
Do not have access to the DOM.

Service Workers are a cornerstone technology for building Progressive Web Apps (PWAs), applications that can be installed on a device and run offline, offering a native-like experience.

And if you're looking to transform your application into a state-of-the-art Progressive Web App (PWA), Service Workers are the pivotal key.

Installing and running a Service Worker

First, you'll need to create a JavaScript file that will serve as your Service Worker. Let's call this file service-worker.js. In this file, you can specify what happens when various lifecycle events occur.

// service-worker.js
self.addEventListener('install', (event) => {
    // Perform install steps
    // e.g. caching important files
});

self.addEventListener('fetch', (event) => {
    // Handle fetch events
    // e.g. serving cached files or making a network request
});

// ... other events like 'activate', 'push', etc.
Enter fullscreen mode Exit fullscreen mode

To make your web application use the Service Worker, you need to register it. This is usually done within your main JavaScript file.

// Check if service workers are supported
if ('serviceWorker' in navigator) {
    // Register the service worker
    navigator.serviceWorker.register('/service-worker.js')
        .then((registration) => {
            console.log('Service Worker registered with scope:', registration.scope);
        })
        .catch((error) => {
            console.log('Service Worker registration failed:', error);
        });
}
Enter fullscreen mode Exit fullscreen mode

And that’s it!
Now you are ready to write some logic for your Service Worker. 🚀🚀🚀

Service Worker Lifecycle

Service Worker Lifecycle

Registration:

The first step in using a Service Worker is to register it.
This is typically done in your main JavaScript file.
The navigator.serviceWorker.register() method is used for this purpose. It returns a Promise that resolves with a ServiceWorkerRegistration object.

Installation:

After successful registration and download, the Service Worker enters the install phase.
An install event is fired at this point, which you can listen for in the Service Worker script.
If everything in the install event is successful, the Service Worker moves to the next phase. If not, it's discarded.

Activation:

After installation, the Service Worker enters the activate phase.
The activate event is fired, allowing you to perform tasks like cleaning up old caches.
A Service Worker will control pages once it's activated. If there's a currently active Service Worker, the new one won't activate until the current one is released from controlling the pages.

Idle:

After activation, the Service Worker enters an idle state. It's alive but not processing any tasks.
The browser can terminate it to save memory but can be quickly restarted when needed.

Termination:

If a Service Worker is idle for too long or uses too much memory, the browser may terminate it. It will be restarted the next time it's needed.

Update:

When a new version of a Service Worker is available (detected during registration), it's installed in parallel to the current one but won't be activated until the current one releases control.
This ensures that only one version of a Service Worker is active at a time for a given scope.

Redundant:

If there's an error during the installation or if the Service Worker is replaced by a newer version, it enters a redundant state and won't control pages.

Service Workers and caching

Service workers help web apps by intercepting network requests and using cached resources. This makes apps load and work faster. When there's no internet, service workers let the app run by getting everything from the cache.

The most popular caching strategies are:

  1. Cache First (Cache, falling back to Network):
    • The service worker checks the cache first.
    • If the requested resource is found in the cache, it's returned immediately.
    • If not, the service worker fetches it from the network, caches it for future use, and then returns it to the client.
    • This strategy is great for static assets that don't change often.
  2. Network First (Network, falling back to Cache):
    • The service worker tries to fetch the resource from the network first.
    • If the network request succeeds, the fetched resource is returned.
    • If the network request fails (e.g., no internet connection), the resource is fetched from the cache.
    • Suitable for resources that update frequently.
  3. Cache Only:
    • The service worker retrieves the resource only from the cache.
    • If the resource isn't in the cache, an error is returned.
    • Useful for resources that are always cached (e.g., core app shell components).
  4. Network Only:
    • The service worker bypasses the cache and fetches the resource only from the network.
    • Useful for requests that don't need to be cached or can't be cached (e.g., real-time updates).
  5. Cache and Network Race (Fastest):
    • The service worker initiates requests to both the cache and the network simultaneously.
    • The first response to arrive (either from the cache or the network) is returned.
    • Useful when speed is crucial, and you want the fastest source to win.
  6. Stale While Revalidate:
    • The service worker returns the resource from the cache immediately (if available) and, in parallel, fetches a fresh copy from the network to update the cache.
    • This ensures that the user gets a quick response, and the cache is kept up-to-date for future requests.


Example: Stale while Revalidate strategy

const CACHE_NAME = 'my-cache';

self.addEventListener('fetch', event => {
  event.respondWith(staleWhileRevalidate(event.request));
});

async function staleWhileRevalidate(request) {
  const cachedResponse = await caches.match(request);

  // If the response exists in the cache, return it
  if (cachedResponse) {
    // Fetch a fresh copy in the background to update the cache
    updateCache(request);
    return cachedResponse;
  }

  // If not in cache, fetch from network
  return fetch(request);
}

function updateCache(request) {
  fetch(request).then(networkResponse => {
    // Open the cache and put the fresh response in it
    caches.open(CACHE_NAME).then(cache => {
      cache.put(request, networkResponse);
    });
  });
}
Enter fullscreen mode Exit fullscreen mode

Push Notifications

Real-time push notifications with service workers can be a powerful feature for web applications, especially for engaging users with timely updates.

How Real-Time Push Notifications works

  1. User Consent: Before sending push notifications, you need to get the user's permission. This is typically done through a prompt asking the user to allow notifications.
  2. Push Subscription: Once the user consents, the service worker subscribes to push notifications using the Push API. This generates a unique endpoint for the user's device.
  3. Sending Notifications: When a notification needs to be sent, your server sends a push message to the push service provided by the browser, which then delivers the message to the correct client.
  4. Receiving Notifications: The service worker receives the push message and can show a notification to the user, even if the web app is not open in the browser at that time.

Use Cases for Push Notifications

  • Chat Applications: Notifying users of new messages or calls when they are not currently looking at the chat application.
  • News or Blog updates: Informing users about new articles or breaking news.
  • Social Media: Updates on new posts, comments, likes, or friend requests.
  • E-commerce: Alerts about sales, order status updates, or new product launches.

  
Example: show notification with the Service Worker

self.addEventListener('push', function(event) {
  const options = {
    body: event.data.text(),
    // Other options like icon, image, etc.
  };

  event.waitUntil(
    self.registration.showNotification('Notification Title', options)
  );
});
Enter fullscreen mode Exit fullscreen mode

Background Sync

It allows web applications to perform tasks in the background, even when the user is not actively using the app or when the app is not open in a browser tab. It's especially useful for scenarios where data needs to be synchronized with a server or other background tasks need to be executed.

How Background Sync works

  1. Queueing: When your web app needs to perform a background task, such as sending data to a server, it creates a background sync event and adds it to a queue managed by the service worker. The event includes details about the task to be performed.
  2. Network Connection: The service worker waits for a suitable network connection to become available.
  3. Execution: Once a suitable network connection is detected, the service worker retrieves the queued background sync events from the queue and executes them.

Use Cases for Background Sync

  • Form Submissions: Background Sync is useful for ensuring that form submissions are not lost, even if the user loses their connection during the submission process.
  • Offline Data Sync: One of the most common use cases is offline data synchronization. For example, consider a note-taking app. When a user creates or edits a note while offline, the app can queue these changes as background sync events.
  • Scheduled Tasks: Background Sync is not limited to data synchronization. It can also be used for scheduling and executing periodic background tasks, such as data cleanup, database maintenance, or any other background processes required by your app.


Example: send form data using background sync

// In a React component

function handleSubmit(formData) {
  if (navigator.onLine) {
    // If there is internet connectivity, perform a regular form submission
    sendFormDataToServer(formData)
      .then(() => {
        console.log('Form submitted successfully.');
      })
      .catch((error) => {
        console.error('Form submission failed:', error);
      });
  } else {
    // If there's no internet connectivity, save the data to the IndexedDB
    // and queue it for background sync.
    ... // Save data to the IndexedDB
    handleSyncData();
  }
}
Enter fullscreen mode Exit fullscreen mode
// In a React component

function handleSyncData() {
  if ('serviceWorker' in navigator && 'SyncManager' in window) {
    navigator.serviceWorker.ready
      .then((registration) => {
        return registration.sync.register('sync-data');
      })
      .catch((error) => {
        console.error('Failed to register background sync:', error);
      });
  } else {
    // Fallback for browsers that don't support background sync.
    // You can implement an alternative offline strategy here.
  }
}
Enter fullscreen mode Exit fullscreen mode
// service-worker.js

self.addEventListener('sync', (event) => {
  if (event.tag === 'sync-data') {
    event.waitUntil(syncDataWithServer());
  }
});

function syncDataWithServer() {
  // Retrieve the queued data from the IndexedDB
  const formData = ...

  if (!formData) {
    return;
  }

  return fetch('/api/sync', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(formData),
  })
    .then((response) => {
      if (response.ok) {
        // Data synchronization was successful; you can clear the data from the IndexedDB
        ...
        console.log('Data synced successfully.');
      } else {
        // Handle synchronization failure, e.g., retry or logging errors.
        console.error('Data sync failed:', response.status);
      }
    })
    .catch((error) => {
      console.error('Error while syncing data:', error);
    });
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

The integration of Service Workers into web applications marks a revolutionary step in enhancing user experience and functionality.

By leveraging their ability to work offline, manage caching strategies, and implement push notifications and background sync, Service Workers empower developers to create more resilient and user-friendly web applications.

This technology not only improves the performance of web apps under challenging network conditions but also opens up new possibilities for real-time interactions and data management.

As the web continues to evolve, embracing Service Workers will be essential for developers looking to build progressive, efficient, and engaging web applications.

The journey into the world of Service Workers is an exciting one, filled with opportunities to innovate and improve the way users interact with web technology. 😊

Top comments (4)

Collapse
 
davboy profile image
Daithi O’Baoill

Nice post, thanks

Collapse
 
markoarsenal profile image
Marko Rajević

It’s my pleasure

Collapse
 
wbrunovieira profile image
WALTER BRUNO PRADO VIEIRA

Thanks for share !

Collapse
 
markoarsenal profile image
Marko Rajević

You are welcome