Come sincronizzare periodicamente i dati in background

Cecilia Cong
Cecilia Cong

Il modo moderno

La sincronizzazione periodica in background ti consente di mostrare contenuti aggiornati quando viene avviata un'app web progressiva o una pagina supportata dai worker di un servizio. Per farlo, scarica i dati in background quando l'app o la pagina non sono in uso.

Utilizzo dell'API Periodic Background Sync

Dopo l'installazione del service worker, utilizza l'APIPermissions per eseguire una query per periodic-background-sync. Puoi eseguire questa operazione da una finestra o da un contesto di service worker.

const status = await navigator.permissions.query({
  name: 'periodic-background-sync',
});
if (status.state === 'granted') {
  // Periodic background sync can be used.
} else {
  // Periodic background sync cannot be used.
}

La registrazione di una sincronizzazione periodica richiede sia un tag sia un intervallo di sincronizzazione minimo (minInterval). Il tag identifica la sincronizzazione registrata per consentire la registrazione di più sincronizzazioni. Nell'esempio riportato di seguito, il nome del tag è 'content-sync' e minInterval è di un giorno.

navigator.serviceWorker.ready.then(async registration => {
  try {
    await registration.periodicSync.register('get-cats', { minInterval: 24 * 60 * 60 * 1000 });
    console.log(Periodic background sync registered.');
  } catch (err) {
    console.error(err.name, err.message);
  }
});

Richiama periodicSync.getTags() per recuperare un array di tag di registrazione. L'esempio riportato di seguito utilizza i nomi dei tag per confermare che l'aggiornamento della cache sia attivo ed evitare di ripeterlo.

const registration = await navigator.serviceWorker.ready;
if ('periodicSync' in registration) {
  const tags = await registration.periodicSync.getTags();
  // Only update content if sync isn't set up.
  if (!tags.includes('content-sync')) {
    updateContentOnPageLoad();
  }
} else {
  // If periodic background sync isn't supported, always update.
  updateContentOnPageLoad();
}

Per rispondere a un evento di sincronizzazione in background periodico, aggiungi un gestore di eventi periodicsync al worker del servizio. L'oggetto evento passato all'oggetto conterrà un parametro tag corrispondente al valore utilizzato durante la registrazione. Ad esempio, se è stata registrata una sincronizzazione in background periodica con il nome 'content-sync', event.tag sarà 'content-sync'.

self.addEventListener('periodicsync', (event) => {
  if (event.tag === 'content-sync') {
    event.waitUntil(syncContent());
  }
});

Compatibilità del browser

Supporto dei browser

  • 80
  • 80
  • x
  • x

Fonte

Il modo classico

Anziché aggiornare i dati in background in modo che siano pronti quando l'utente carica l'app, il metodo classico consiste semplicemente nell'aggiornare i dati al caricamento.

Per approfondire

Demo

HTML

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link
      rel="icon"
      href=""
    />
    <link rel="manifest" href="./manifest.json" />
    <title>How to periodically synchronize data in the background</title>
    <link rel="stylesheet" href="/style.css" />
    <!-- TODO: Devsite - Removed inline handlers -->
    <!-- <script src="/script.js" defer></script> -->
  </head>
  <body>
    <h1>How to periodically synchronize data in the background</h1>
    <p class="available">Periodic background sync can be used. Install the app first.</p>
    <p class="not-available">Periodic background sync cannot be used.</p>
    <h2>Last updated</h2>
    <p class="last-updated">Never</p>
    <h2>Registered tags</h2>
    <ul>
      <li>None yet.</li>
    </ul>
  </body>
</html>

CSS


        html {
  box-sizing: border-box;
  font-family: system-ui, sans-serif;
  color-scheme: dark light;
}

*,
*:before,
*:after {
  box-sizing: inherit;
}

body {
  margin: 1rem;
}
        

JS


        const available = document.querySelector('.available');
const notAvailable = document.querySelector('.not-available');
const ul = document.querySelector('ul');
const lastUpdated = document.querySelector('.last-updated');

const updateContent = async () => {
  const data = await fetch(
    'https://worldtimeapi.org/api/timezone/Europe/London.json'
  ).then((response) => response.json());
  return new Date(data.unixtime * 1000);
};

const registerPeriodicBackgroundSync = async (registration) => {
  const status = await navigator.permissions.query({
    name: 'periodic-background-sync',
  });
  if (status.state === 'granted' && 'periodicSync' in registration) {
    try {
      // Register the periodic background sync.
      await registration.periodicSync.register('content-sync', {
        // An interval of one day.
        minInterval: 24 * 60 * 60 * 1000,
      });
      available.hidden = false;
      notAvailable.hidden = true;

      // List registered periodic background sync tags.
      const tags = await registration.periodicSync.getTags();
      if (tags.length) {
        ul.innerHTML = '';
      }
      tags.forEach((tag) => {
        const li = document.createElement('li');
        li.textContent = tag;
        ul.append(li);
      });

      // Update the user interface with the last periodic background sync data.
      const backgroundSyncCache = await caches.open('periodic-background-sync');
      if (backgroundSyncCache) {
        const backgroundSyncResponse =
          backgroundSyncCache.match('/last-updated');
        if (backgroundSyncResponse) {
          lastUpdated.textContent = `${await fetch('/last-updated').then(
            (response) => response.text()
          )} (periodic background-sync)`;
        }
      }

      // Listen for incoming periodic background sync messages.
      navigator.serviceWorker.addEventListener('message', async (event) => {
        if (event.data.tag === 'content-sync') {
          lastUpdated.textContent = `${await updateContent()} (periodic background sync)`;
        }
      });
    } catch (err) {
      console.error(err.name, err.message);
      available.hidden = true;
      notAvailable.hidden = false;
      lastUpdated.textContent = 'Never';
    }
  } else {
    available.hidden = true;
    notAvailable.hidden = false;
    lastUpdated.textContent = `${await updateContent()} (manual)`;
  }
};

if ('serviceWorker' in navigator) {
  window.addEventListener('load', async () => {
    const registration = await navigator.serviceWorker.register('./sw.js');
    console.log('Service worker registered for scope', registration.scope);

    await registerPeriodicBackgroundSync(registration);
  });
}