Service Worker Sample: Fallback Response Sample

Available in Chrome 42+ | View on GitHub | Browse Samples

Background

This sample demonstrates basic service worker registration, with the service worker providing fallback responses when specific HTTP requests fail. This is done within the fetch handler of the service worker, and will only work for responses that are not opaque, since we need to examine the response's HTTP status code.

Live Output

Each of these buttons will trigger a CORS-enabled HTTP request to fetch a list of videos from the YouTube Data API. One button makes a valid request, and the other makes an invalid request that always trigger an error response (due to an invalid API key). The service worker takes care of translating the error response into a valid fallback response, which is treated by the host page exactly like any other valid response.


This Page's JavaScript

function enableRequestButtons() {
  var validButton = document.querySelector('#valid-api-call');
  validButton.addEventListener('click', function() {
    // This is a valid YouTube API key, and should result in a valid API request.
    makeApiRequest('AIzaSyCr0XVB-Hz1ohPpjvLatdj4qZ5zcSohHsU');
  });
  validButton.disabled = false;

  var invalidButton = document.querySelector('#invalid-api-call');
  invalidButton.addEventListener('click', function() {
    // This is an (obviously) invalid YouTube API key, and should result in an invalid API request.
    makeApiRequest('INVALID_API_KEY');
  });
  invalidButton.disabled = false;
}

function makeApiRequest(apiKey) {
  var url = 'https://www.googleapis.com/youtube/v3/playlistItems?part=snippet' +
    '&maxResults=3&playlistId=UU_x5XG1OV2P6uZZ5FSM9Ttw&key=' + apiKey;

  fetch(url).then(function(response) {
    return response.json();
  }).then(function(json) {
    var titles = json.items.map(function(item) {
      return '"' + item.snippet.title + '"';
    }).join(', ');

    ChromeSamples.setStatus('The three most recent videos are: ' + titles);
  });
}

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('service-worker.js');

  // Enable the buttons once the service worker has taken control of the page.
  navigator.serviceWorker.ready.then(enableRequestButtons);
} else {
  ChromeSamples.setStatus('This browser does not support service workers.');
}

Service Worker's JavaScript

/*
 Copyright 2015 Google Inc. All Rights Reserved.
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 http://www.apache.org/licenses/LICENSE-2.0
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
*/

self.addEventListener('install', function(event) {
  // Skip the 'waiting' lifecycle phase, to go directly from 'installed' to 'activated', even if
  // there are still previous incarnations of this service worker registration active.
  event.waitUntil(self.skipWaiting());
});

self.addEventListener('activate', function(event) {
  // Claim any clients immediately, so that the page will be under SW control without reloading.
  event.waitUntil(self.clients.claim());
});

self.addEventListener('fetch', function(event) {
  var regex = /https:\/\/www.googleapis.com\/youtube\/v3\/playlistItems/;
  if (event.request.url.match(regex)) {
    // Only call event.respondWith() if this looks like a YouTube API request.
    // Because we don't call event.respondWith() for non-YouTube API requests, they will not be
    // handled by the service worker, and the default network behavior will apply.
    event.respondWith(
      fetch(event.request).then(function(response) {
        if (!response.ok) {
          // An HTTP error response code (40x, 50x) won't cause the fetch() promise to reject.
          // We need to explicitly throw an exception to trigger the catch() clause.
          throw Error('response status ' + response.status);
        }

        // If we got back a non-error HTTP response, return it to the page.
        return response;
      }).catch(function(error) {
        console.warn('Constructing a fallback response, ' +
          'due to an error while fetching the real response:', error);

        // For demo purposes, use a pared-down, static YouTube API response as fallback.
        var fallbackResponse = {
          items: [{
            snippet: {title: 'Fallback Title 1'}
          }, {
            snippet: {title: 'Fallback Title 2'}
          }, {
            snippet: {title: 'Fallback Title 3'}
          }]
        };

        // Construct the fallback response via an in-memory variable. In a real application,
        // you might use something like `return fetch(FALLBACK_URL)` instead,
        // to retrieve the fallback response via the network.
        return new Response(JSON.stringify(fallbackResponse), {
          headers: {'Content-Type': 'application/json'}
        });
      })
    );
  }
});