Des réponses immédiates en streaming

Quiconque a utilisé des service workers peut vous dire qu'il est asynchrone. Elles s'appuient exclusivement sur des interfaces basées sur les événements, comme FetchEvent, et utilisent des promesses pour signaler la fin d'opérations asynchrones.

L'asynchronisme est tout aussi importante, bien que moins visible pour le développeur, en ce qui concerne les réponses fournies par le gestionnaire d'événements d'extraction d'un service worker. Les réponses en flux continu sont ici la norme: elles permettent à la page à l'origine de la requête d'origine de commencer à travailler avec la réponse dès que le premier fragment de données est disponible, et peuvent éventuellement utiliser des analyseurs optimisés pour le streaming afin d'afficher progressivement le contenu.

Lorsque vous écrivez votre propre gestionnaire d'événements fetch, il est courant de transmettre simplement à la méthode respondWith() un Response (ou une promesse pour un Response) que vous obtenez via fetch() ou caches.match(), et de l'appeler un jour. La bonne nouvelle, c'est que les Response créés par ces deux méthodes sont déjà diffusables. La mauvaise nouvelle, c'est que les vidéos créées manuellement Responsene peuvent pas être diffusées en streaming, du moins jusqu'à présent. C'est là que l'API Streams entre en jeu.

Des flux ?

Un flux est une source de données qui peut être créée et manipulée de manière incrémentielle. Elle fournit une interface pour la lecture ou l'écriture de fragments de données asynchrones, dont seul un sous-ensemble peut être disponible en mémoire à un moment donné. Pour l'instant, nous nous intéressons aux ReadableStream, qui permettent de construire un objet Response transmis à 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 page dont la requête a déclenché l'événement fetch reçoit une réponse en flux continu dès que la méthode event.respondWith() est appelée, et continue à lire ce flux tant que le service worker continue d'envoyer des données (enqueue()) supplémentaires. La réponse du service worker vers la page est vraiment asynchrone et nous contrôlons entièrement le remplissage du flux.

Utilisations réelles

Vous avez probablement remarqué que l'exemple précédent comportait des commentaires d'espace réservé /* your data here */ et que les détails d'implémentation réels étaient limités. À quoi ressemblerait un exemple concret ?

Jake Archibald a (sans surprise) un excellent exemple d'utilisation de flux pour assembler une réponse HTML à partir de plusieurs extraits HTML mis en cache, ainsi que des données "en direct" diffusées via fetch() (dans ce cas, du contenu de son blog).

Comme Jake l'explique, l'avantage d'une réponse en flux continu est que le navigateur peut analyser et afficher le code HTML en cours de diffusion, y compris le bit initial qui est chargé rapidement à partir du cache, sans avoir à attendre la fin de l'extraction de l'ensemble du contenu du blog. Cela permet de tirer pleinement parti des fonctionnalités d'affichage HTML progressif du navigateur. D'autres ressources qui peuvent également être affichées progressivement, comme certains formats d'image et de vidéo, peuvent également bénéficier de cette approche.

Des flux ? Ou des shells d'applications ?

Les bonnes pratiques existantes concernant l'utilisation de service workers pour alimenter vos applications Web se concentrent sur un modèle shell d'application + contenu dynamique. Cette approche repose sur une mise en cache agressive de l'interface système de votre application Web (le minimum de code HTML, JavaScript et CSS nécessaire pour afficher votre structure et votre mise en page), puis sur le chargement du contenu dynamique nécessaire à chaque page spécifique via une requête côté client.

Les flux apportent avec eux une alternative au modèle App Shell, dans lequel une réponse HTML plus complète est diffusée au navigateur lorsqu'un utilisateur accède à une nouvelle page. La réponse diffusée peut utiliser des ressources mises en cache, ce qui permet de fournir rapidement le fragment de code HTML initial, même hors connexion. Toutefois, elles finissent par ressembler à des corps de réponse traditionnels affichés par le serveur. Par exemple, si votre application Web est alimentée par un système de gestion de contenu qui affiche le code HTML par le serveur en assemblant des modèles partiels, ce modèle se traduit directement par l'utilisation de réponses en flux continu, la logique de création de modèles étant répliquée dans le service worker plutôt que dans votre serveur. Comme le montre la vidéo suivante, pour ce cas d'utilisation, l'avantage en termes de vitesse sans frais par les réponses diffusées en streaming peut être frappant:

L'un des principaux avantages de diffuser l'intégralité de la réponse HTML, qui explique pourquoi il s'agit de l'alternative la plus rapide dans la vidéo, est que le code HTML affiché lors de la requête de navigation initiale peut tirer pleinement parti de l'analyseur HTML en flux continu du navigateur. Les fragments de code HTML insérés dans un document après le chargement de la page (comme c'est courant dans le modèle App Shell) ne peuvent pas bénéficier de cette optimisation.

Si vous en êtes à la phase de planification de la mise en œuvre de votre service worker, quel modèle devez-vous adopter: des réponses diffusées progressivement, ou un shell léger associé à une requête de contenu dynamique côté client ? La réponse à cette question est sans surprise: selon que vous disposez d'une implémentation existante qui repose sur un CMS et des modèles partiels (avantage: flux), que vous attendez des charges utiles HTML uniques et volumineuses qui bénéficieraient d'un affichage progressif (avantage: flux), que votre application Web est conçue pour une application monopage (avantage: App Shell) et si vous avez besoin d'un modèle compatible avec plusieurs navigateurs: la compatibilité avec plusieurs navigateurs.

Nous n'en sommes qu'aux débuts des réponses par flux fournies par les service workers. Nous avons hâte de voir les différents modèles mûrir, et en particulier de voir davantage d'outils développés pour automatiser les cas d'utilisation courants.

Explorer les diffusions en détail

Si vous créez vos propres flux lisibles, appeler controller.enqueue() sans discernement peut ne pas être suffisant ni efficace. Jake explique en détail comment les méthodes start(), pull() et cancel() peuvent être utilisées en tandem pour créer un flux de données adapté à votre cas d'utilisation.

Si vous souhaitez obtenir encore plus de détails, la spécification Streams peut vous aider.

Compatibilité

La création d'un objet Response dans un service worker utilisant un ReadableStream comme source a été ajoutée dans Chrome 52.

L'implémentation du service worker de Firefox n'est pas encore compatible avec les réponses reposant sur des ReadableStream, mais vous pouvez suivre un bug de suivi pertinent pour l'API Streams.

La progression de la compatibilité avec l'API Streams sans préfixe dans Edge, ainsi que la compatibilité globale des service workers, peuvent être suivies sur la page d'état de la plate-forme de Microsoft.