Introduzione al recupero in background

Jake Archibald
Jake Archibald

Nel 2015 abbiamo introdotto la sincronizzazione in background, che consente al service worker di posticipare il lavoro finché l'utente non ha connettività. Ciò significa che l'utente può digitare un messaggio, premere Invia e uscire dal sito sapendo che il messaggio verrà inviato ora o quando avrà una connessione.

È una funzionalità utile, ma richiede che il service worker sia attivo per tutta la durata del recupero. Non è un problema per attività brevi come l'invio di un messaggio, ma se l'attività richiede troppo tempo, il browser interromperà il service worker, altrimenti la privacy e la batteria dell'utente sono a rischio.

Cosa succede se devi scaricare qualcosa che potrebbe richiedere molto tempo, come un film, podcast o livelli di un gioco? A questo serve il recupero dello sfondo.

Background Fetch è disponibile per impostazione predefinita a partire da Chrome 74.

Ecco una breve demo di due minuti che mostra la situazione tradizionale rispetto all'utilizzo di Background Fetch:

Come funziona

Il recupero dello sfondo funziona nel seguente modo:

  1. Indichi al browser di eseguire un gruppo di recuperi in background.
  2. Il browser recupera questi elementi, mostrando all'utente lo stato di avanzamento.
  3. Una volta completato o non riuscito il recupero, il browser apre il service worker e attiva un evento per comunicarti cosa è successo. Qui decidi cosa fare con le risposte, se necessario.

Se l'utente chiude le pagine del tuo sito dopo il passaggio 1, non preoccuparti, il download continuerà. Poiché il recupero è altamente visibile e facilmente annullabile, non esiste il problema di privacy di un'attività di sincronizzazione in background troppo lunga. Poiché il service worker non è in esecuzione costante, non c'è il rischio che possa abusare del sistema, ad esempio estraendo bitcoin in background.

Su alcune piattaforme (come Android) è possibile che il browser si chiuda dopo il passaggio 1, in quanto può delegare il recupero al sistema operativo.

Se l'utente avvia il download mentre è offline o passa offline durante il download, il recupero in background verrà messo in pausa e ripreso in un secondo momento.

L'API

Rilevamento delle funzionalità

Come per qualsiasi nuova funzionalità, devi rilevare se il browser la supporta. Per il recupero dello sfondo, è semplice come:

if ('BackgroundFetchManager' in self) {
  // This browser supports Background Fetch!
}

Avviare un recupero dello sfondo

L'API principale dipende dalla registrazione di un service worker, quindi assicurati di aver registrato prima un service worker. Quindi:

navigator.serviceWorker.ready.then(async (swReg) => {
  const bgFetch = await swReg.backgroundFetch.fetch('my-fetch', ['/ep-5.mp3', 'ep-5-artwork.jpg'], {
    title: 'Episode 5: Interesting things.',
    icons: [{
      sizes: '300x300',
      src: '/ep-5-icon.png',
      type: 'image/png',
    }],
    downloadTotal: 60 * 1024 * 1024,
  });
});

backgroundFetch.fetch accetta tre argomenti:

Parametri
id string
identifica in modo univoco questo recupero in background.

backgroundFetch.fetch verrà rifiutato se l'ID corrisponde a un recupero in background esistente.

requests Array<Request|string>
Gli elementi da recuperare. Le stringhe verranno trattate come URL e trasformate in Request tramite new Request(theString).

Puoi recuperare elementi da altre origini a condizione che le risorse lo consentano tramite CORS.

Nota: Chrome al momento non supporta le richieste che richiederebbero un preflight CORS.

options Un oggetto che può includere quanto segue:
options.title string
Un titolo da visualizzare nel browser insieme all'avanzamento.
options.icons Array<IconDefinition>
Un array di oggetti con `src`, `size` e `type`.
options.downloadTotal number
Le dimensioni totali dei corpi delle risposte (dopo la decompressione).

Sebbene sia facoltativo, ti consigliamo vivamente di fornirlo. Viene utilizzato per comunicare all'utente le dimensioni del download e per fornire informazioni sullo stato di avanzamento. Se non fornisci questo valore, il browser comunicherà all'utente che le dimensioni non sono note e, di conseguenza, l'utente potrebbe essere più propenso ad annullare il download.

Se i download di recupero in background superano il numero indicato qui, verranno interrotti. Non c'è alcun problema se il download è inferiore a downloadTotal, quindi se non sai quale sarà il totale del download, è meglio non rischiare.

backgroundFetch.fetch restituisce una promessa che si risolve con un BackgroundFetchRegistration. Approfondiremo i dettagli in un secondo momento. La promessa viene rifiutata se l'utente ha disattivato i download o se uno dei parametri forniti non è valido.

Fornire molte richieste per un singolo recupero in background consente di combinare elementi che per l'utente sono logicamente un'unica cosa. Ad esempio, un film può essere suddiviso in migliaia di risorse (tipico di MPEG-DASH) e includere risorse aggiuntive come le immagini. Un livello di un gioco potrebbe essere distribuito su molte risorse JavaScript, immagini e audio. Per l'utente, invece, è semplicemente "il film" o "il livello".

Ottenere un recupero dello sfondo esistente

Puoi ottenere un recupero in background esistente in questo modo:

navigator.serviceWorker.ready.then(async (swReg) => {
  const bgFetch = await swReg.backgroundFetch.get('my-fetch');
});

… passando l'id del recupero in background che vuoi. get restituisce undefined se non è presente un recupero in background attivo con questo ID.

Un recupero in background è considerato "attivo" dal momento della registrazione fino al completamento, all'interruzione o all'esito negativo.

Puoi ottenere un elenco di tutti i recuperi in background attivi utilizzando getIds:

navigator.serviceWorker.ready.then(async (swReg) => {
  const ids = await swReg.backgroundFetch.getIds();
});

Registrazioni del recupero dello sfondo

Un BackgroundFetchRegistration (bgFetch negli esempi precedenti) ha le seguenti caratteristiche:

Proprietà
id string
L'ID del recupero in background.
uploadTotal number
Il numero di byte da inviare al server.
uploaded number
Il numero di byte inviati correttamente.
downloadTotal number
Il valore fornito al momento della registrazione del recupero in background o zero.
downloaded number
Il numero di byte ricevuti correttamente.

Questo valore potrebbe diminuire. Ad esempio, se la connessione si interrompe e il download non può essere ripreso, il browser riavvia il recupero della risorsa da zero.

result

Il valore sarà uno dei seguenti:

  • "": il recupero in background è attivo, quindi non ci sono ancora risultati.
  • "success" - Il recupero in background è riuscito.
  • "failure" - Il recupero in background non è riuscito. Questo valore viene visualizzato solo quando il recupero in background non riesce completamente, ad esempio il browser non può riprovare/riprendere.
failureReason

Il valore sarà uno dei seguenti:

  • "" - Il recupero in background non è riuscito.
  • "aborted": il recupero in background è stato interrotto dall'utente oppure è stato chiamato abort().
  • "bad-status": una delle risposte aveva uno stato non OK, ad esempio 404.
  • "fetch-error" - Uno dei recuperi non è riuscito per un altro motivo, ad esempio CORS, MIX, una risposta parziale non valida o un errore di rete generale per un recupero che non può essere ritentato.
  • "quota-exceeded" - La quota di spazio di archiviazione è stata raggiunta durante il recupero in background.
  • "download-total-exceeded" - Il valore `downloadTotal` fornito è stato superato.
recordsAvailable boolean
È possibile accedere alle richieste/risposte sottostanti?

Una volta impostato su false, match e matchAll non possono essere utilizzati.

Metodi
abort() Restituisce Promise<boolean>
Interrompe il recupero in background.

La promessa restituita viene risolta con il valore true se il recupero è stato interrotto correttamente.

matchAll(request, opts) Restituisce Promise<Array<BackgroundFetchRecord>>
Get the requests and responses.

Gli argomenti qui sono gli stessi dell'API cache. La chiamata senza argomenti restituisce una promessa per tutti i record.

Per ulteriori dettagli, continua a leggere.

match(request, opts) Restituisce Promise<BackgroundFetchRecord>
Come sopra, ma risolve con la prima corrispondenza.
Eventi
progress Attivato quando cambia uno dei valori uploaded, downloaded, result o failureReason.

Monitorare i progressi

Questa operazione può essere eseguita tramite l'evento progress. Ricorda che downloadTotal è il valore che hai fornito o 0 se non hai fornito un valore.

bgFetch.addEventListener('progress', () => {
  // If we didn't provide a total, we can't provide a %.
  if (!bgFetch.downloadTotal) return;

  const percent = Math.round(bgFetch.downloaded / bgFetch.downloadTotal * 100);
  console.log(`Download progress: ${percent}%`);
});

Ottenere le richieste e le risposte

bgFetch.match('/ep-5.mp3').then(async (record) => {
  if (!record) {
    console.log('No record found');
    return;
  }

  console.log(`Here's the request`, record.request);
  const response = await record.responseReady;
  console.log(`And here's the response`, response);
});

record è un BackgroundFetchRecord e ha questo aspetto:

Proprietà
request Request
La richiesta fornita.
responseReady Promise<Response>
La risposta recuperata.

La risposta è in attesa perché potrebbe non essere ancora stata ricevuta. La promessa verrà rifiutata se il recupero non va a buon fine.

Eventi service worker

Eventi
backgroundfetchsuccess Tutto è stato recuperato correttamente.
backgroundfetchfailure Uno o più recuperi non sono riusciti.
backgroundfetchabort Uno o più recuperi non sono riusciti.

Questa opzione è utile solo se vuoi eseguire la pulizia dei dati correlati.

backgroundfetchclick L'utente ha fatto clic sull'UI di avanzamento del download.

Gli oggetti evento hanno le seguenti caratteristiche:

Proprietà
registration BackgroundFetchRegistration
Metodi
updateUI({ title, icons }) Consente di modificare il titolo e le icone impostati inizialmente. Questo passaggio è facoltativo, ma ti consente di fornire maggiori dettagli, se necessario. Puoi farlo solo *una volta* durante gli eventi backgroundfetchsuccess e backgroundfetchfailure.

Reagire a successi e insuccessi

Abbiamo già visto l'evento progress, ma è utile solo quando l'utente ha una pagina aperta sul tuo sito. Il vantaggio principale del recupero in background è che le cose continuano a funzionare dopo che l'utente ha lasciato la pagina o anche chiuso il browser.

Se il recupero in background viene completato correttamente, il service worker riceverà l'evento backgroundfetchsuccess e event.registration sarà la registrazione del recupero in background.

Dopo questo evento, le richieste e le risposte recuperate non sono più accessibili, quindi se vuoi conservarle, spostale in un'altra posizione, ad esempio nell'API cache.

Come per la maggior parte degli eventi service worker, utilizza event.waitUntil in modo che il service worker sappia quando l'evento è completato.

Ad esempio, nel service worker:

addEventListener('backgroundfetchsuccess', (event) => {
  const bgFetch = event.registration;

  event.waitUntil(async function() {
    // Create/open a cache.
    const cache = await caches.open('downloads');
    // Get all the records.
    const records = await bgFetch.matchAll();
    // Copy each request/response across.
    const promises = records.map(async (record) => {
      const response = await record.responseReady;
      await cache.put(record.request, response);
    });

    // Wait for the copying to complete.
    await Promise.all(promises);

    // Update the progress notification.
    event.updateUI({ title: 'Episode 5 ready to listen!' });
  }());
});

L'errore potrebbe essere dovuto a un singolo 404, che potrebbe non essere importante per te, quindi potrebbe valere comunque la pena copiare alcune risposte in una cache come sopra.

Reagire al clic

L'interfaccia utente che mostra l'avanzamento e il risultato del download è selezionabile. L'evento backgroundfetchclick nel service worker ti consente di reagire a questo problema. Come sopra, event.registration sarà la registrazione del recupero in background.

La cosa più comune da fare con questo evento è aprire una finestra:

addEventListener('backgroundfetchclick', (event) => {
  const bgFetch = event.registration;

  if (bgFetch.result === 'success') {
    clients.openWindow('/latest-podcasts');
  } else {
    clients.openWindow('/download-progress');
  }
});

Risorse aggiuntive

Correzione: una versione precedente di questo articolo faceva erroneamente riferimento a Background Fetch come "standard web". L'API non è attualmente in fase di standardizzazione. La specifica è disponibile in WICG come bozza di report del gruppo della community.