Trasmetti in streaming per risposte immediate

Chiunque abbia utilizzato i service worker potrebbe dirti che sono asincroni fino in fondo. Si basano esclusivamente su interfacce basate su eventi, come FetchEvent, e utilizzano le promise per indicare quando le operazioni asincrone sono complete.

L'asincronicità è ugualmente importante, anche se meno visibile per lo sviluppatore, quando si tratta di risposte fornite dal gestore eventi di recupero di un servizio worker. Le risposte in streaming sono lo standard di riferimento in questo caso: consentono alla pagina che ha effettuato la richiesta originale di iniziare a utilizzare la risposta non appena è disponibile il primo blocco di dati e potenzialmente di utilizzare parser ottimizzati per lo streaming per visualizzare progressivamente i contenuti.

Quando scrivi il tuo gestore eventi fetch, è comune passare al metodo respondWith() un Response (o una promessa di un Response) che ottieni tramite fetch() o caches.match(), e non fare altro. La buona notizia è che i Response creati con entrambi questi metodi sono già riproducibili in streaming. La cattiva notizia è che i Response "costruiti manualmente" non sono riproducibili in streaming, almeno finora. È qui che entra in gioco l'API Streams.

Stream?

Uno stream è un'origine dati che può essere creata e manipolata in modo incrementale, e fornisce un'interfaccia per leggere o scrivere blocchi di dati asincroni, di cui solo un sottoinsieme potrebbe essere disponibile in memoria in un determinato momento. Per il momento, ci interessano i ReadableStream, che possono essere utilizzati per creare 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 di streaming non appena viene chiamato event.respondWith() e continuerà a leggere da quel stream finché il service worker continuerà a enqueue() dati aggiuntivi. La risposta che passa dal service worker alla pagina è veramente asincrona e abbiamo il controllo completo sul riempimento dello stream.

Utilizzi reali

Probabilmente hai notato che l'esempio precedente conteneva alcuni commenti placeholder/* your data here */ e non forniva molti dettagli sull'implementazione effettiva. Come sarebbe un esempio reale?

Jake Archibald (non a caso) ha fornito un ottimo esempio dell'utilizzo degli stream per unire una risposta HTML da più snippet HTML memorizzati nella cache, insieme a dati "in tempo reale" in streaming tramite fetch(): in questo caso, contenuti per il suo blog.

Il vantaggio dell'utilizzo di una risposta in streaming, come spiegato da Jake, è che il browser può analizzare e visualizzare il codice HTML man mano che viene visualizzato, incluso il bit iniziale che viene caricato rapidamente dalla cache, senza dover attendere il completamento del recupero dell'intero contenuto del blog. In questo modo, vengono sfruttate appieno le funzionalità di rendering HTML progressivo del browser. Anche altre risorse che possono essere visualizzate progressivamente, come alcuni formati di immagini e video, possono trarre vantaggio da questo approccio.

Stream? Oppure shell di app?

Le best practice esistenti sull'utilizzo dei worker di servizio per potenziare le app web si concentrano su un modello di app shell + contenuti dinamici. Questo approccio si basa sulla memorizzazione nella cache aggressiva della "shell" dell'applicazione web, ovvero il codice HTML, JavaScript e CSS minimo necessario per visualizzare la struttura e il layout, e poi carica i contenuti dinamici necessari per ogni pagina specifica tramite una richiesta lato client.

Gli stream offrono un'alternativa al modello App Shell, in cui viene inviata al browser una risposta HTML più completa quando un utente passa a una nuova pagina. La risposta in streaming può utilizzare le risorse memorizzate nella cache, quindi può fornire rapidamente il blocco iniziale di HTML, anche offline, ma alla fine assomiglia di più ai corpi di risposta tradizionali sottoposti a rendering sul server. Ad esempio, se la tua app web è basata su un sistema di gestione dei contenuti che esegue il rendering HTML lato server combinando modelli parziali, questo modello si traduce direttamente nell'utilizzo di risposte in streaming, con la logica di creazione dei modelli replicata nel service worker anziché nel server. Come dimostra il video seguente, per questo caso d'uso, il vantaggio in termini di velocità offerto dalle risposte in streaming può essere sorprendente:

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

Pertanto, se ti trovi nella fase di pianificazione dell'implementazione del tuo worker di servizio, quale modello dovresti adottare: risposte in streaming che vengono visualizzate progressivamente o un shell leggero abbinato a una richiesta lato client per contenuti dinamici? La risposta, non sorprendentemente, è che dipende: se hai già un'implementazione basata su un CMS e su modelli parziali (vantaggio: stream); se prevedi payload HTML singoli e di grandi dimensioni che potrebbero trarre vantaggio dal rendering progressivo (vantaggio: stream); se è meglio modellare la tua app web come un'applicazione a pagina singola (vantaggio: App Shell); e se hai bisogno di un modello attualmente supportato nelle release stabili di più browser (vantaggio: App Shell).

Siamo ancora agli inizi delle risposte dinamiche basate su service worker e non vediamo l'ora di vedere i diversi modelli maturare e soprattutto di vedere più strumenti sviluppati per automatizzare i casi d'uso comuni.

Approfondimento degli stream

Se stai creando i tuoi stream leggibili, chiamare semplicementecontroller.enqueue() in modo indiscriminato potrebbe non essere sufficiente o efficiente. Jake entra nei dettagli su come i metodi start(), pull() e cancel() possono essere utilizzati in combinazione per creare uno stream di dati personalizzato per il tuo caso d'uso.

Per chi vuole ancora più dettagli, la specifica di Streams è la soluzione ideale.

Compatibilità

In Chrome 52 è stato aggiunto il supporto per la creazione di un oggetto Response all'interno di un worker di servizio che utilizza un ReadableStream come origine.

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

I progressi relativi al supporto dell'API Streams senza prefisso in Edge, nonché al supporto complessivo dei service worker, possono essere monitorati nella pagina Stato della piattaforma di Microsoft.