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 essere informati del risultato. Ecco alcuni esempi:

  • Una pagina invia al service worker un elenco di URL da precaricare in modo che, quando l'utente fa clic su un link, le sottorisorse del documento o della pagina siano già disponibili nella cache, velocizzando così la navigazione successiva.
  • La pagina chiede al service worker di recuperare e memorizzare nella cache un insieme dei principali articoli per renderli disponibili offline.

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

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

In questa guida esploreremo come implementare una tecnica di comunicazione unidirezionale dalla pagina al service worker utilizzando le API del browser standard e la libreria di Workbox. Questi tipi di casi d'uso saranno definiti memorizzazione nella cache imperativa.

Caso di produzione

1-800-Flowers.com ha implementato la cache imperativa (precaricamento) con i service worker tramite postMessage() per precaricare gli elementi principali nelle pagine di categoria e velocizzare la successiva navigazione verso le pagine dei dettagli dei prodotti.

Logo di 1-800 fiori.

Usano un approccio misto per decidere quali elementi precaricare:

  • Al momento del caricamento della pagina, chiede al servicer worker di recuperare i dati JSON dei primi 9 elementi e di aggiungere gli oggetti di risposta risultanti alla cache.
  • Per gli altri elementi, ascolta l'evento mouseover in modo che, quando un utente sposta il cursore sopra un elemento, possa attivare un recupero per la risorsa on demand.

Utilizzano l'API Cache per archiviare le risposte JSON:

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

Quando l'utente fa clic su un elemento, i dati JSON associati possono essere recuperati dalla cache, senza bisogno di accedere 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 agli altri pacchetti Workbox in esecuzione nel service worker.

Per comunicare la pagina con il service worker, ottieni prima un riferimento all'oggetto Workbox al service worker registrato:

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

Quindi puoi inviare direttamente il messaggio in modo dichiarativo, senza doversi preoccupare di ottenere la registrazione, controllare l'attivazione o pensare all'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 la comunicazione window to service worker utilizzando le API del browser.

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

La pagina chiama postMessage() nell'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 diversi tipi di istruzioni al service worker (ossia "per precaricare" e "per liberare 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à usufruirne dei 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, la pagina non ha bisogno di sapere se il service worker ha avuto esito positivo. In questo caso, l'utente potrà navigare più rapidamente. In caso contrario, la pagina deve comunque passare alla nuova pagina. Ci vorrà più tempo.

Un semplice esempio di precaricamento

Una delle applicazioni più comuni di memorizzazione nella cache imperativa è il precaricamento, cioè il recupero delle risorse per un determinato URL prima che l'utente vi acceda, in modo da velocizzare la navigazione.

Esistono diversi modi per implementare il precaricamento nei siti:

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

Se è necessaria una logica aggiuntiva, ad esempio l'analisi della risorsa di precaricamento (un file o una pagina JSON) per recuperare i relativi URL interni, è più opportuno delegare completamente questa attività al service worker.

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

  • Attribuire il carico di lavoro impegnativo dell'elaborazione di recupero e post-caricamento (che verrà introdotto in seguito) su un thread secondario. In questo modo, il thread principale potrà gestire le attività più importanti, come la risposta alle interazioni degli utenti.
  • Consentire a più client (ad es. le schede) di riutilizzare una funzionalità comune e persino chiamare il servizio contemporaneamente senza bloccare il thread principale.

Precarica le pagine dei dettagli del prodotto

Innanzitutto utilizza postMessage() nell'interfaccia del service worker e passa un array di URL alla 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 dell'array di URL ed emettere una richiesta di recupero per ciascuno di essi:

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

Quando si ottiene la risposta, puoi fare affidamento sulle intestazioni di memorizzazione nella cache della risorsa. In molti casi, tuttavia, come nelle pagine dei dettagli dei prodotti, le risorse non vengono memorizzate nella cache (ovvero hanno un'intestazione Cache-control di no-cache). In questi casi, puoi ignorare questo comportamento archiviando la risorsa recuperata nella cache del service worker. Ciò offre il vantaggio aggiuntivo di consentire la pubblicazione del file in scenari offline.

Oltre i dati JSON

Una volta recuperati da un endpoint del server, i dati JSON spesso contengono altri URL che vale la pena precaricare, ad esempio un'immagine o altri dati di endpoint associati a questi dati di primo livello.

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

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

Modifica il codice fetchAsync() per eseguire l'iterazione sull'elenco dei prodotti e memorizzare nella cache l'immagine hero per ciascuno 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 alcune eccezioni alla gestione di questo codice in situazioni quali gli errori 404. Tuttavia, l'aspetto positivo dell'utilizzo di un service worker per il precaricamento è che può avere esito negativo senza molte conseguenze sulla pagina e sul thread principale. Potresti anche avere una logica più elaborata nella post-elaborazione dei contenuti precaricati, il che li rende più flessibili e disaccoppiati dai dati che vengono gestiti. Non ci sono limiti.

Conclusione

In questo articolo abbiamo trattato un caso d'uso comune di comunicazione unidirezionale tra la pagina e il worker del servizio: memorizzazione nella cache imperativa. Gli esempi discussi hanno il solo scopo di dimostrare un modo di utilizzare questo pattern e lo stesso approccio può essere applicato anche ad altri casi d'uso, ad esempio la memorizzazione nella cache degli articoli più letti on demand per l'utilizzo offline, l'impostazione di preferiti e altro ancora.

Per altri pattern di comunicazione dei worker di pagina e dei servizi, consulta:

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