- Le standard Web Streams a été conçu pour assurer un streaming de données cohérent entre navigateur et serveur, mais il dégrade aujourd’hui l’expérience développeur en raison de sa complexité et de ses limites de performance
- L’API existante impose des contraintes de conception comme la gestion des verrous (lock), le BYOB et la backpressure, qui créent une charge inutile tant pour l’usage que pour l’implémentation
- Cloudflare propose un nouveau modèle de flux fondé sur l’itération asynchrone (async iteration), qui affiche des performances 2 fois à 120 fois supérieures
- La nouvelle API améliore l’efficacité et la cohérence grâce à une structure simple d’async iterable, des politiques explicites de backpressure et la prise en charge parallèle du synchrone et de l’asynchrone
- Cette approche pourrait permettre un modèle de streaming unifié sur tous les runtimes, notamment Node.js, Deno, Bun et les navigateurs, et servir de point de départ à de futures discussions de standardisation
Limites structurelles de Web Streams
- Le standard WHATWG Streams a été développé entre 2014 et 2016 avec une conception centrée sur le navigateur ; comme l’async iteration n’existait pas encore à l’époque, il a introduit un modèle distinct de reader/writer
- Cela a entraîné des procédures inutiles comme la gestion des verrous, des boucles de lecture complexes et le traitement des buffers BYOB
- Le modèle de verrouillage (locking) monopolise un flux et empêche la consommation parallèle ; si
releaseLock() est omis, le flux peut rester verrouillé de façon permanente
- La fonctionnalité BYOB (Bring Your Own Buffer) visait la réutilisation mémoire, mais son modèle complexe de séparation et de transfert des buffers la rend peu utilisée en pratique et difficile à implémenter
- La backpressure est théoriquement prise en charge, mais sa structure ne permet pas de contrôle réel, par exemple
enqueue() réussit même quand la valeur desiredSize est négative
- Chaque appel à
read() force la création d’une Promise, ce qui provoque une baisse de performances et une charge GC dans les scénarios de streaming à haute fréquence
Problèmes constatés en pratique
- Si le corps de réponse de
fetch() n’est pas consommé, cela peut épuiser le pool de connexions ; et l’usage de tee() entraîne un buffering mémoire illimité
TransformStream traite immédiatement sans tenir compte de l’état de préparation à la lecture, ce qui provoque une explosion du buffer quand le consommateur est lent
- En rendu côté serveur (SSR), le traitement de milliers de petits chunks déclenche un GC thrashing qui fait chuter fortement les performances
- Chaque runtime (Node.js, Deno, Bun, Workers) a introduit des chemins d’optimisation non standard pour atténuer ces problèmes, au prix d’une baisse de compatibilité et de cohérence
- Les Web Platform Tests exigent plus de 70 fichiers de test complexes, reflet d’une gestion interne d’état excessive et de comportements peu intuitifs
Principes de conception de la nouvelle API de flux
- Le flux est défini comme un simple async iterable, consommable directement avec
for await...of
- Elle adopte des transformations pull-through, de sorte que le traitement ne s’exécute que lorsque le consommateur demande des données
- Elle fournit des politiques explicites de backpressure (
strict, block, drop-oldest, drop-newest) pour éviter l’emballement mémoire
- Les données sont transmises sous forme de chunks batchés (
Uint8Array[]) pour réduire le coût de création des Promise
- L’API est simplifiée avec un traitement dédié uniquement aux octets, en supprimant BYOB et les concepts complexes de contrôleur
- Une voie synchrone (synchronous) est prise en charge afin d’éliminer le surcoût des Promise dans les traitements centrés CPU
Exemples et caractéristiques de la nouvelle API
Stream.push() permet de créer simplement une paire writer/readable, et Stream.text() de récupérer tout le texte
Stream.pull() construit une pipeline paresseuse (lazy) qui ne s’exécute qu’au moment de la consommation
Stream.share() et Stream.broadcast() prennent en charge une gestion explicite de plusieurs consommateurs
- Les API sync/async combinées (
Stream.pullSync(), Stream.textSync()) maximisent les performances pour les opérations sans I/O
- Pour l’interopérabilité avec Web Streams, la conversion est possible via de simples fonctions d’adaptation
Comparaison de performances et perspectives
- Des benchmarks sur Node.js montrent un traitement jusqu’à 80 à 90 fois plus rapide, et dans les navigateurs plus de 100 fois plus rapide
- Exemple : 275GB/s contre 3GB/s sur une chaîne de transformation en 3 étapes
- Ces gains de performances viennent de la suppression du surcoût asynchrone, du traitement par lots et d’une conception fondée sur le pull
- Cette implémentation est écrite en pur TypeScript/JavaScript, avec un potentiel d’amélioration supplémentaire via une implémentation native
- Cloudflare présente cette approche comme un point de départ pour la discussion autour d’un standard et sollicite les retours de la communauté des développeurs
Conclusion
- Web Streams était une solution raisonnable compte tenu des contraintes de l’époque, mais ne correspond plus aux fonctionnalités du JavaScript moderne ni aux pratiques actuelles de développement
- Le nouveau modèle fondé sur les async iterables réunit simplicité, performance et contrôle explicite, et ouvre la voie à un écosystème de streaming cohérent entre runtimes
- Cloudflare publie une implémentation de référence, de la documentation et des exemples de code sur GitHub dans jasnell/new-streams
- L’objectif n’est pas de définir immédiatement un nouveau standard, mais de poser un point de départ concret pour discuter d’une “meilleure API de flux”
Aucun commentaire pour le moment.