Improving JavaScript streams API: Why we need it

Developers working with streaming data know how essential it is to handle data effectively. That’s where the WHATWG Streams Standard, commonly referred to as “Web streams,” comes into play. This standard was created to provide a common API for streaming data that can work seamlessly across different browsers and servers. It has been integrated into browsers, Cloudflare Workers, Node.js, Deno, and Bun, and has even served as the foundation for APIs like fetch(). But despite its widespread adoption, I’ve encountered some usability and performance issues while working with Web streams over the years.

As someone who has implemented Web streams in various environments, dealt with production issues, and helped developers navigate common pitfalls, I’ve realized that there are some fundamental challenges with the current API. These issues aren’t bugs but rather consequences of design decisions made a decade ago. In today’s JavaScript development landscape, these design choices don’t quite align with how developers write code.

In my exploration of Web streams, I’ve found an alternative approach centered around JavaScript language features, showcasing that a better solution is possible. When benchmarked, this alternative outperforms Web streams by a significant margin, running between 2x to 120x faster across multiple runtimes, including Cloudflare Workers, Node.js, Deno, Bun, and major browsers. These improvements aren’t just about optimizations but stem from embracing modern JavaScript language features in a different way.

The Streams Standard was crafted between 2014 and 2016 with the goal of creating APIs for working with streaming data efficiently. At the time, there was no standard way to handle streaming data on the web. Web streams were designed independently of the existing Node.js streaming API to cater exclusively to the needs of web browsers. The timing of the Streams Standard predated the introduction of async iteration in JavaScript, which later impacted how developers consume async sequences in JavaScript.

One of the primary issues with Web streams is the excessive ceremony required for basic operations like reading streams. The process of acquiring a reader, managing locks, and handling data in chunks adds unnecessary complexity to streaming operations. Although async iteration was eventually added to Web streams to simplify this process, it was a retrofit onto an existing API, leading to limitations and hidden complexities.

Moreover, Web streams utilize a locking model to prevent multiple consumers from interfering with data reads. This locking mechanism can be problematic, as forgetting to release locks or dealing with pending reads can permanently break a stream. While the concept of locking is essential for data integrity, the manual implementation of locks using methods like getReader() and releaseLock() poses challenges for developers.

In conclusion, while the Web streams API has served its purpose, there’s room for improvement in terms of usability and performance. By reevaluating design choices and leveraging modern JavaScript language features more effectively, we can potentially pave the way for a more streamlined and efficient streaming experience in the future.