Introduzione al recupero in background

Jake Archibald
Jake Archibald

Nel 2015 abbiamo introdotto la sincronizzazione in background, che consente al servizio worker di rimandare il lavoro fino a quando l'utente non ha connettività. Ciò significa che l'utente può digitare un messaggio, premere Invia e lasciare il sito sapendo che il messaggio verrà inviato ora o quando è disponibile la connettività.

Si tratta di una funzionalità utile, ma richiede che il service worker sia attivo per la durata del recupero. Questo non è un problema per brevi operazioni come l'invio di un messaggio, ma se l'attività richiede troppo tempo il browser terminerà il service worker, altrimenti potrebbe mettere a rischio la privacy e la batteria dell'utente.

E se dovessi scaricare qualcosa che potrebbe richiedere molto tempo, ad esempio un film, podcast o livelli di un gioco? Ecco a cosa serve il recupero in background.

Il recupero in background è disponibile per impostazione predefinita da Chrome 74.

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

Prova la demo e esamina il codice.

Come funziona

Un recupero in background funziona nel seguente modo:

  1. Chiedi al browser di eseguire un gruppo di recuperi in background.
  2. Il browser recupera questi elementi, mostrando i progressi all'utente.
  3. Una volta completato o non riuscito il recupero, il browser apre il tuo service worker e attiva un evento per informarti di cosa è successo. Qui decidi cosa fare con le risposte, se vuoi.

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

Su alcune piattaforme (ad esempio Android) è possibile che il browser si chiuda dopo il passaggio 1, in quanto il browser può trasferire 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 di funzionalità

Come per qualsiasi nuova funzionalità, devi rilevare se il browser la supporta. Per il recupero in background, è sufficiente:

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

Avvio di un recupero in background

L'API principale si basa su una registrazione di un service worker, quindi assicurati di aver prima registrato 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 viene 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 convertite in Request tramite new Request(theString).

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

Nota: al momento Chrome non supporta le richieste che richiedono 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 gli attributi "src", "size" e "type".
options.downloadTotal number
Le dimensioni totali dei corpi delle risposte (dopo lo sgonfiamento).

Sebbene sia facoltativo, ti consigliamo vivamente di fornirlo. Viene utilizzato per indicare all'utente le dimensioni del download e per fornire informazioni sui progressi. Se non fornisci questa informazione, il browser comunicherà all'utente che le dimensioni sono sconosciute 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, l'operazione verrà interrotta. È completamente normale se il download è inferiore a downloadTotal, quindi se non hai la certezza del totale del download, è meglio errare sul lato della cautela.

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

Se fornisci molte richieste per un singolo recupero in background, puoi combinare elementi che logicamente sono un singolo elemento per l'utente. Ad esempio, un film può essere suddiviso in migliaia di risorse (come accade tipicamente con MPEG-DASH) e includere risorse aggiuntive come le immagini. Un livello di un gioco potrebbe essere distribuito su molte risorse JavaScript, immagini e audio. Ma per l'utente è semplicemente "il film" o "il livello".

Recuperare un recupero in background esistente

Puoi recuperare un recupero in background esistente come questo:

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

…passando l'id del recupero in background che ti interessa. get restituisce undefined se non è presente alcun recupero in background attivo con quell'ID.

Un recupero in background è considerato "attivo" dal momento in cui viene registrato fino a quando non va a buon fine, non va a buon fine o viene interrotto.

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 in background

Un BackgroundFetchRegistration (bgFetch negli esempi precedenti) ha quanto segue:

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 dello sfondo è attivo, quindi non è ancora disponibile alcun risultato.
  • "success" - Il recupero in background è andato a buon fine.
  • "failure" - Il recupero in background non è riuscito. Questo valore viene visualizzato solo quando il recupero in background non va a buon fine, ad esempio quando il browser non può riprovare/riprendere.
failureReason

Il valore sarà uno dei seguenti:

  • "": il recupero in background non ha avuto esito negativo.
  • "aborted": il recupero in background è stato interrotto dall'utente o è stata chiamata la funzione abort().
  • "bad-status" - Una delle risposte aveva uno stato non OK, ad esempio 404.
  • "fetch-error" - Uno dei recuperi non è riuscito per qualche altro motivo, ad esempio CORS, MIX, una risposta parziale non valida o un errore di rete generale per un recupero per il quale non è possibile riprovare.
  • "quota-exceeded" - La quota di spazio di archiviazione è stata raggiunta durante il recupero in background.
  • "download-total-exceeded": il valore fornito per "downloadTotal" è stato superato.
recordsAvailable boolean
È possibile accedere alle richieste/risposte sottostanti?

Se questo valore è falso, non è possibile utilizzare match e matchAll.

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

La promessa restituita si risolve con true se il recupero è stato interrotto correttamente.

matchAll(request, opts) Restituisce Promise<Array<BackgroundFetchRecord>>
Ottieni le richieste e le risposte.

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.

Monitoraggio dei progressi

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

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}%`);
});

Ricevere 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 il seguente aspetto:

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

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

Eventi del service worker

Eventi
backgroundfetchsuccess Tutti gli elementi sono stati recuperati correttamente.
backgroundfetchfailure Uno o più recuperi non sono riusciti.
backgroundfetchabort Uno o più recuperi non sono riusciti.

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

backgroundfetchclick L'utente ha fatto clic sull'interfaccia utente relativa all'avanzamento del download.

Gli oggetti evento hanno quanto segue:

Proprietà
registration BackgroundFetchRegistration
Metodi
updateUI({ title, icons }) Consente di modificare il titolo/le icone impostati inizialmente. Questa operazione è facoltativa, ma ti consente di fornire un contesto più ampio, se necessario. Puoi farlo solo *una volta* durante gli eventi backgroundfetchsuccess e backgroundfetchfailure.

Reagire a successi/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 esce dalla pagina o addirittura chiude il browser.

Se il recupero in background viene completato correttamente, il tuo 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 luogo come l'API cache.

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

Ad esempio, nel tuo 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 codice 404, che potrebbe non essere stato importante per te, quindi potrebbe essere comunque utile copiare alcune risposte in una cache come sopra.

Reagire al clic

L'interfaccia utente che mostra lo stato di avanzamento e il risultato del download è cliccabile. L'evento backgroundfetchclick nel service worker ti consente di reagire a questo comportamento. 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 a uno "standard web". Al momento l'API non è inclusa nel canale standard, ma la specifica è disponibile nel WICG come bozza del report del gruppo della community.