This article critiques the existing WHATWG Streams Standard (Web streams) in JavaScript, highlighting its fundamental usability and performance issues stemming from design decisions made before modern JavaScript features like async iteration. It proposes an alternative API design that leverages current language primitives, demonstrating significant performance improvements and reduced complexity for common streaming operations. The discussion provides valuable insights into API design trade-offs and the impact of evolving language features on architectural choices.
Read original on Cloudflare BlogThe WHATWG Streams Standard aimed to provide a universal API for handling streaming data across browsers and server-side runtimes like Node.js, Deno, and Cloudflare Workers. While ambitious and foundational for many modern web APIs (e.g., fetch()), the standard's design choices, particularly its development predating JavaScript's `async iteration` (ES2018), have led to significant usability and performance challenges. These challenges are not bugs, but rather inherent consequences of architectural decisions that no longer align with contemporary JavaScript development patterns.
API Design Principle: Align with Language Idioms
The evolution of programming language features significantly impacts the optimal design of foundational APIs. APIs designed before the availability of idiomatic language constructs (like `async iteration`) often accrue complexity and hinder adoption, even if they address real technical challenges.
The article advocates for an alternative streaming API design that more effectively leverages modern JavaScript language features. By building on `async iteration` as a core primitive, many of the complexities around manual reader acquisition, lock management, and the `value, done` protocol can be abstracted away, resulting in cleaner, more readable code. This approach not only simplifies the developer experience but also demonstrates significant performance gains (2x to 120x faster in benchmarks), attributed to fundamental design differences rather than micro-optimizations. This highlights how revisiting fundamental architectural decisions with current technological capabilities can lead to vastly superior outcomes.
// Web streams (before async iteration retrofitting)
const reader = stream.getReader();
const chunks = [];
try {
while (true) {
const { value, done } = await reader.read();
if (done) break;
chunks.push(value);
}
} finally {
reader.releaseLock();
}
// Web streams with async iteration (retro-fitted)
const chunks = [];
for await (const chunk of stream) {
chunks.push(chunk);
}
// Proposed alternative (conceptual, leveraging modern primitives for simpler, faster streaming)