Trasmetti in streaming per risposte immediate

Jacopo Posnick
Jacopo Posnick

Chiunque utilizzi i service worker può dirti che sono asincroni fino in fondo. Si basano esclusivamente su interfacce basate su eventi, come FetchEvent, e utilizzano le promesse per segnalare il completamento delle operazioni asincrone.

L'asincronia è altrettanto importante, anche se meno visibile per lo sviluppatore, quando si tratta delle risposte fornite dal gestore di eventi fetch di un service worker. Le risposte in modalità flusso sono lo standard principale in questo caso: consentono alla pagina che ha effettuato la richiesta originale di iniziare a lavorare con la risposta non appena è disponibile il primo blocco di dati e potenzialmente utilizzano parser ottimizzati per il flusso di dati per visualizzare progressivamente i contenuti.

Quando si scrive un gestore di eventi fetch personalizzato, è normale trasmettere al metodo respondWith() una Response (o una promessa per Response) che si ottiene tramite fetch() o caches.match() e chiamarlo "un giorno". La buona notizia è che i Response creati con entrambi questi metodi sono già riproducibili in streaming. La cattiva notizia è che i contenuti Responsecreati manualmente non sono riproducibili in streaming, almeno fino al momento. È qui che entra in gioco l'API Streams.

Stream?

Un flusso è un'origine dati che può essere creata e manipolata in modo incrementale e fornisce un'interfaccia per la lettura o la scrittura di blocchi di dati asincroni, solo un sottoinsieme dei quali potrebbe essere disponibile in memoria in un determinato momento. Per ora, siamo interessati a ReadableStream, che possono essere utilizzati per costruire un oggetto Response che viene passato a fetchEvent.respondWith():

self.addEventListener('fetch', event => {
    var stream = new ReadableStream({
    start(controller) {
        if (/* there's more data */) {
        controller.enqueue(/* your data here */);
        } else {
        controller.close();
        }
    });
    });

    var response = new Response(stream, {
    headers: {'content-type': /* your content-type here */}
    });

    event.respondWith(response);
});

La pagina la cui richiesta ha attivato l'evento fetch riceverà una risposta in modalità flusso non appena viene chiamata event.respondWith() e continuerà a leggere dal flusso finché il service worker continua a enqueue()inviare dati aggiuntivi. La risposta che passa dal service worker alla pagina è realmente asincrona e noi abbiamo il controllo completo dell'evasione dello stream.

Utilizzi reali

Probabilmente avrai notato che l'esempio precedente conteneva alcuni commenti segnaposto /* your data here */ e aveva pochi dettagli di implementazione effettivi. Quale sarebbe un esempio reale?

Jake Archibald (non sorprende!) ha un ottimo esempio di utilizzo degli stream per unire una risposta HTML da più snippet HTML memorizzati nella cache, insieme a dati "live" trasmessi in streaming tramite fetch(), in questo caso i contenuti per il suo blog

Il vantaggio dell'utilizzo di una risposta in streaming, come spiega Jake, è che il browser può analizzare e visualizzare il codice HTML durante lo streaming, incluso il bit iniziale che viene caricato rapidamente dalla cache, senza dover attendere il completamento dell'intero recupero dei contenuti del blog. Ciò sfrutta al massimo le capacità di rendering HTML progressivo del browser. Anche altre risorse che possono essere visualizzate in modo progressivo, come alcuni formati di immagini e video, possono trarre vantaggio da questo approccio.

Stream? O le shell dell'app?

Le best practice esistenti sull'utilizzo dei service worker per potenziare le app web si concentrano su un modello App Shell + contenuti dinamici. Questo approccio si basa sulla memorizzazione nella cache aggressiva della "shell" della tua applicazione web (il codice HTML, JavaScript e CSS minimi necessari per visualizzare la struttura e il layout) e il caricamento dei contenuti dinamici necessari per ogni pagina specifica tramite una richiesta lato client.

I flussi offrono un'alternativa al modello App Shell, in cui viene trasmessa al browser una risposta HTML più completa quando un utente visita una nuova pagina. La risposta in streaming può fare uso di risorse memorizzate nella cache, quindi può comunque fornire rapidamente il blocco iniziale di HTML, anche offline, ma sembra più simile a corpi di risposta tradizionali sottoposti a rendering server. Ad esempio, se la tua app web si basa su un sistema di gestione dei contenuti che esegue il rendering del codice HTML unendo modelli parziali, quel modello si traduce direttamente nell'utilizzo di risposte in modalità flusso, con la logica dei modelli replicata nel service worker anziché nel server. Come dimostra il seguente video, per quel caso d'uso il vantaggio in termini di velocità offerto dalle risposte in streaming può essere notevole:

Un importante vantaggio dello streaming dell'intera risposta HTML, spiegando perché è l'alternativa più veloce nel video, è che il rendering HTML visualizzato durante la richiesta di navigazione iniziale può sfruttare appieno l'analizzatore sintattico HTML di streaming del browser. I blocchi di codice HTML inseriti in un documento dopo il caricamento della pagina (come accade nel modello App Shell) non possono usufruire di questa ottimizzazione.

Quindi, se sei nelle fasi di pianificazione dell'implementazione del service worker, quale modello dovresti adottare: risposte in streaming visualizzate progressivamente o una shell leggera accoppiata a una richiesta lato client per contenuti dinamici? La risposta è, non sorprende, che dipende: dalla tua implementazione esistente che si basa su un CMS e su modelli parziali (vantaggio: flusso); dalla tua previsione di payload HTML singoli e di grandi dimensioni che trarrebbero vantaggio dal rendering progressivo (vantaggio: flusso), dal fatto che la tua app web sia o meno modellata come applicazione a pagina singola (vantaggio: App Shell); e dal fatto che sia necessaria una versione stabile di più browser su Shell.

Siamo ancora agli inizi delle risposte in streaming basate sui service worker e non vediamo l'ora di vedere maturare i diversi modelli e, in particolare, di sviluppare altri strumenti per automatizzare i casi d'uso più comuni.

Approfondimento sugli stream

Se stai creando i tuoi flussi leggibili personali, la semplice chiamata a controller.enqueue() in modo indiscriminato potrebbe non essere sufficiente o efficiente. Jake approfondisce alcuni dettagli su come i metodi start(), pull() e cancel() possono essere utilizzati in tandem per creare uno stream di dati su misura per il tuo caso d'uso.

Per chi desidera ulteriori dettagli, la specifica Streams fa al caso tuo.

Compatibilità

Il supporto per la creazione di un oggetto Response all'interno di un service worker utilizzando ReadableStream come origine è stato aggiunto in Chrome 52.

L'implementazione del service worker di Firefox non supporta ancora le risposte supportate da ReadableStream, ma esiste un bug di monitoraggio pertinente per il supporto dell'API Streams che puoi seguire.

L'avanzamento del supporto dell'API Streams senza prefisso in Edge, insieme al supporto complessivo dei service worker, può essere monitorato nella pagina Stato della piattaforma di Microsoft.