Guida alla memorizzazione nella cache imperativa

Andrew Guan
Andrew Guan
Demián Renzulli
Demián Renzulli

Alcuni siti web potrebbero dover comunicare con il service worker senza dover informato del risultato. Ecco alcuni esempi:

  • Una pagina invia al service worker un elenco di URL a di precaricamento in modo che, quando l'utente fa clic su un collegare il documento o le risorse secondarie della pagina sono già disponibili nella cache, rendendo le molto più velocemente.
  • La pagina chiede al service worker di recuperare e memorizzare nella cache un insieme di articoli principali, disponibili offline.

La delega di questi tipi di attività non critiche al service worker ha il vantaggio di liberare thread principale per gestire meglio le attività più urgenti, come la risposta alle interazioni degli utenti.

Diagramma di una pagina che richiede risorse da memorizzare nella cache per un service worker.

In questa guida esamineremo come implementare una tecnica di comunicazione unidirezionale dalla pagina per il service worker usando le API del browser standard e la libreria Workbox. Chiameremo questi tipi casi d'uso relativi alla memorizzazione nella cache imperativa.

Caso di produzione

1-800-Flowers.com ha implementato la memorizzazione nella cache imperativa (precaricamento) con i service worker tramite postMessage() per precaricare articoli principali nelle pagine delle categorie per velocizzare la navigazione successiva alle pagine dei dettagli dei prodotti.

Logo di 1-800 fiori.

Utilizzano un approccio misto per decidere quali elementi precaricare:

  • Durante il caricamento della pagina, chiedono al servicer worker di recuperare i dati JSON per i primi 9 elementi. aggiungere alla cache gli oggetti di risposta risultanti.
  • Per gli elementi rimanenti, ascolta l'evento mouseover in modo tale che, quando l'utente sposta il cursore sopra un elemento e può attivare un recupero per la risorsa on "demand".

Utilizzano l'API Cache per archiviare i file JSON risposte:

Logo di 1-800 fiori.
Precaricamento dei dati di prodotto JSON dalle pagine delle schede di prodotto di 1-800Flowers.com.

Quando l'utente fa clic su un articolo, i dati JSON associati possono essere recuperati dalla cache, senza doverti collegare alla rete, rendendo la navigazione più veloce.

Utilizzo di Workbox

Workbox offre un modo semplice per inviare messaggi a un service worker, tramite il pacchetto workbox-window, un insieme di moduli da eseguire nel contesto della finestra. Si integrano con gli altri pacchetti Workbox eseguite nel service worker.

Per comunicare la pagina al service worker, devi prima ottenere un riferimento all'oggetto Workbox al service worker registrato:

const wb = new Workbox('/sw.js');
wb.register();

Quindi è possibile inviare direttamente il messaggio in modo dichiarativo, senza doversi preoccupare di ottenere il la registrazione, il controllo dell'attivazione o l'uso dell'API di comunicazione sottostante:

wb.messageSW({"type": "PREFETCH", "payload": {"urls": ["/data1.json", "data2.json"]}}); });

Il service worker implementa un gestore message per ascoltare questi messaggi. Può facoltativamente restituire una risposta, anche se, in casi come questi, non necessario:

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'PREFETCH') {
    // do something
  }
});

Utilizzo delle API del browser

Se la libreria Workbox non è sufficiente per le tue esigenze, ecco come puoi implementare il servizio window-to-service la comunicazione tra i worker, utilizzando le API del browser.

L'API postMessage può essere utilizzato per stabilire un meccanismo di comunicazione unidirezionale dalla pagina al service worker.

La pagina chiama postMessage() il dell'interfaccia del service worker:

navigator.serviceWorker.controller.postMessage({
  type: 'MSG_ID',
  payload: 'some data to perform the task',
});

Il service worker implementa un gestore message per ascoltare questi messaggi.

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === MSG_ID) {
    // do something
  }
});

L'attributo {type : 'MSG_ID'} non è assolutamente obbligatorio, ma è un modo per consentire alla pagina di inviare al service worker tipi diversi di istruzioni (ovvero "precaricare" invece di "cancellare" spazio di archiviazione"). Il service worker può diramarsi in diversi percorsi di esecuzione in base a questo flag.

Se l'operazione ha esito positivo, l'utente potrà trarne i vantaggi, ma in caso contrario il flusso utente principale rimarrà invariato. Ad esempio, quando 1-800-Flowers.com tenta di eseguire la preregistrazione nella cache, non è necessario che la pagina sappia se il service worker è riuscito. In questo caso, l'utente potrà usufruire di una navigazione più veloce. In caso contrario, deve comunque passare alla nuova pagina. Ci vorrà un po' più di tempo.

Un semplice esempio di precaricamento

Una delle applicazioni più comuni della memorizzazione nella cache imperativa è il precaricamento, ovvero il recupero risorse per un determinato URL, prima che l'utente vi passi, al fine di velocizzare la navigazione.

Esistono diversi modi per implementare il precaricamento nei siti:

Per scenari di precaricamento relativamente semplici, come il precaricamento di documenti o asset specifici (JS, CSS e così via), queste tecniche sono l'approccio migliore.

Se è necessaria una logica aggiuntiva, ad esempio l'analisi della risorsa di precaricamento (una pagina o un file JSON) in di recuperare gli URL interni, è più appropriato delegare questa attività interamente al con il Service worker.

La delega di questi tipi di operazioni al service worker presenta i vantaggi seguenti:

  • alleggerire il lavoro di recupero e post-fetch (che verrà introdotto in un secondo momento) a un thread secondario. In questo modo il thread principale potrà essere gestito in modo da ad esempio rispondere alle interazioni degli utenti.
  • Permettendo a più client (ad es. schede) di riutilizzare una funzionalità comune e persino di chiamare senza bloccare il thread principale.

Precarica le pagine dei dettagli dei prodotti

Primo utilizzo di postMessage() il giorno l'interfaccia del service worker e passare un array di URL da memorizzare nella cache:

navigator.serviceWorker.controller.postMessage({
  type: 'PREFETCH',
  payload: {
    urls: [
      'www.exmaple.com/apis/data_1.json',
      'www.exmaple.com/apis/data_2.json',
    ],
  },
});

Nel service worker, implementa un gestore message per intercettare ed elaborare i messaggi inviati da qualsiasi scheda attiva:

addEventListener('message', (event) => {
  let data = event.data;
  if (data && data.type === 'PREFETCH') {
    let urls = data.payload.urls;
    for (let i in urls) {
      fetchAsync(urls[i]);
    }
  }
});

Nel codice precedente abbiamo introdotto una piccola funzione helper chiamata fetchAsync() per eseguire l'iterazione sul di URL ed emetti una richiesta di recupero per ognuno di essi:

async function fetchAsync(url) {
  // await response of fetch call
  let prefetched = await fetch(url);
  // (optionally) cache resources in the service worker storage
}

Una volta ottenuta la risposta, puoi fare affidamento sulle intestazioni di memorizzazione nella cache della risorsa. In molti casi, ma, come nelle pagine dei dettagli dei prodotti, le risorse non vengono memorizzate nella cache (ossia, hanno una Cache-control intestazione di no-cache). In casi come questi puoi ignorare questo comportamento, con l'archiviazione della risorsa recuperata nella cache del service worker. Questo ha l'ulteriore vantaggio di consentire da pubblicare negli scenari offline.

Oltre i dati JSON

Una volta recuperati da un endpoint server, i dati JSON spesso contengono altri URL che sono anch'essi che vale la pena precaricare, ad esempio i dati di un'immagine o di altri endpoint associati a questo e i dati di Google Cloud.

Supponiamo che nel nostro esempio i dati JSON restituiti siano le informazioni di un sito di acquisti di generi alimentari:

{
  "productName": "banana",
  "productPic": "https://cdn.example.com/product_images/banana.jpeg",
  "unitPrice": "1.99"
 }

Modifica il codice fetchAsync() per eseguire l'iterazione nell'elenco dei prodotti e memorizzare nella cache l'immagine hero per ognuno di essi:

async function fetchAsync(url, postProcess) {
  // await response of fetch call
  let prefetched = await fetch(url);

  //(optionally) cache resource in the service worker cache

  // carry out the post fetch process if supplied
  if (postProcess) {
    await postProcess(prefetched);
  }
}

async function postProcess(prefetched) {
  let productJson = await prefetched.json();
  if (productJson && productJson.product_pic) {
    fetchAsync(productJson.product_pic);
  }
}

Puoi aggiungere una gestione delle eccezioni per questo codice in situazioni come gli errori 404. Ma l'aspetto positivo di usare un service worker per il precaricamento è che può avere esito negativo senza molto come conseguenza alla pagina e al thread principale. Potresti anche avere una logica più complessa post-elaborazione dei contenuti precaricati, rendendoli più flessibili e disaccoppiati dai dati gestione dei problemi. Non ci sono limiti.

Conclusione

In questo articolo abbiamo trattato un caso d'uso comune della comunicazione unidirezionale tra pagina e servizio worker: memorizzazione nella cache imperativa. Gli esempi discussi servono solo a dimostrare un modo di utilizzando questo pattern e lo stesso approccio può essere applicato anche ad altri casi d'uso, ad esempio memorizzando nella cache gli articoli principali on demand per l'utilizzo offline, l'aggiunta di segnalibri e altro ancora.

Per ulteriori modelli di comunicazione tra pagine e service worker, dai un'occhiata a:

  • Annunci di aggiornamento: chiamata alla pagina dal service worker per informare su aggiornamenti importanti (ad es. è disponibile una nuova versione dell'app web).
  • Comunicazione bidirezionale: delega di un'attività a un service worker (ad es. un download impegnativo) e mantenere la pagina informata sullo stato di avanzamento.