What is the Locked Property
The locked property is a read-only boolean attribute of the ReadableStream interface that indicates whether the stream is currently locked to an active reader. According to the WHATWG Streams Standard, a readable stream can have at most one active reader at a time, and when such a reader exists, the stream is said to be locked. This state prevents concurrent access and ensures that data consumption follows a controlled, sequential pattern.
When you create a new ReadableStream, the locked property is initially set to false, indicating that no reader has acquired the stream and it is available for consumption. The property transitions to true when an active reader begins consuming data from the stream, and it returns to false only when that reader releases its lock or the stream is closed.
The fundamental purpose of locking is to maintain data integrity and ensure predictable behavior when working with streams. When a stream is locked, the underlying implementation can perform important optimizations, such as directly shuttling data from the source to the consumer while bypassing intermediate queues. This is a core concept in modern JavaScript development that enables efficient data processing pipelines.
The locked property acts as both a protective mechanism and an indicator of stream state, preventing developers from accidentally creating scenarios where multiple consumers compete for the same data. By forcing explicit management of reader locks, the API encourages thoughtful design patterns that handle streaming data in a controlled manner.
Return Value and Data Type
The locked property returns a boolean value that directly reflects the stream's current state regarding reader access. When the return value is true, it indicates that an active reader has acquired the stream and is currently consuming or ready to consume data from it. When the return value is false, the stream is available for a new reader to acquire.
This boolean return value makes it straightforward to check stream availability before attempting to acquire a reader. Developers can implement conditional logic that checks the locked property before calling getReader() or other methods that require the stream to be unlocked. This pattern is particularly valuable when building web applications that involve asynchronous data processing.
1const stream = new ReadableStream({2 start(controller) {3 controller.enqueue('chunk1');4 controller.enqueue('chunk2');5 controller.close();6 }7});8 9// Initially, the stream is not locked10console.log(stream.locked); // false11 12// Acquire a reader13const reader = stream.getReader();14console.log(stream.locked); // true15 16// Check before acquiring another reader17if (!stream.locked) {18 const newReader = stream.getReader();19} else {20 console.log('Stream is currently locked');21}22 23// Release the lock24reader.releaseLock();25console.log(stream.locked); // falseHow Locking Works with Readers and Writers
Understanding the locking mechanism requires familiarity with readers, which are objects that enable direct reading of chunks from a readable stream. A reader is acquired through the stream's getReader() method, and once acquired, it maintains a lock on the stream until explicitly released.
The relationship between readers and locks is one-to-one: each reader corresponds to a single lock, and a stream can have at most one active reader at any time. When you call getReader() on an unlocked stream, the method returns a new reader instance and sets the stream's locked property to true. Any subsequent calls to getReader() while the stream remains locked will throw a TypeError, indicating that the stream is already locked.
For readable byte streams, the API provides two types of readers: default readers and BYOB ("bring your own buffer") readers. Default readers treat chunks as opaque values and work with any chunk type, while BYOB readers allow more efficient reading of byte-oriented data by enabling developers to supply their own buffers. This is particularly useful when building high-performance web applications that process large amounts of data.
The interaction between writable streams and their writers follows similar principles. WritableStream objects also have a locked property that indicates whether the stream is locked to an active writer. When a writer acquires a lock on a writable stream, other writers cannot be acquired until the lock is released.
Operations That Cause a Stream to Become Locked
Several operations can cause a ReadableStream to become locked:
getReader()- The most direct way to acquire a lock. Returns a new reader and sets locked totruepipeTo()- Pipes a readable stream to a writable stream, locking the readable stream for the durationpipeThrough()- Pipes a readable stream through a transform stream, also locking during the operationtee()- Creates two branches of a readable stream, locking the original stream- Async iteration - Using
for await...ofautomatically locks the stream during iteration
Each of these operations has specific implications for how the lock is managed and released. Understanding these operations is crucial for designing applications that handle streaming data correctly, especially when building real-time web solutions.
Follow these guidelines to avoid common pitfalls
Acquire Only When Ready
Get a reader only when you need to consume data, and release immediately when done. Holding locks unnecessarily creates contention.
Use Try-Finally Blocks
Always release locks in finally blocks to ensure cleanup happens even when errors occur during processing.
Leverage Higher-Level Abstractions
Use pipe operations and utility consumers that handle locking automatically, reducing the chance of lock-related errors.
Use Tee for Multiple Consumers
When multiple components need the same data, use tee() to create independent branches that can be read separately.
1// Safe lock acquisition with guaranteed release2async function processStream(stream) {3 const reader = stream.getReader();4 try {5 while (true) {6 const { value, done } = await reader.read();7 if (done) break;8 // Process the chunk...9 console.log(value);10 }11 } finally {12 // Always release the lock, even if an error occurs13 reader.releaseLock();14 }15}16 17// Usage18async function main() {19 const stream = new ReadableStream({ /* ... */ });20 await processStream(stream);21 // Stream is now unlocked and available22}Performance Considerations
The locking mechanism enables important performance optimizations in the Streams API:
- Direct data transfer during pipes: When a readable stream is piped to a writable stream, the lock allows the implementation to bypass intermediate queues and transfer data directly from the source to the sink, reducing memory usage
- Efficient backpressure management: With a single consumer, the implementation can track processing rate and adjust production accordingly without coordinating among multiple consumers
- BYOB readers for byte streams: BYOB ("bring your own buffer") readers minimize memory copies by enabling data to be written directly into developer-supplied memory
However, locking can create bottlenecks when multiple components need access to the same stream. Use tee() to create independent branches when supporting multiple consumers, though this has memory implications as data must be buffered for both branches. These performance considerations are essential when optimizing JavaScript applications for production workloads.
Frequently Asked Questions
What happens if I try to acquire a reader on a locked stream?
Attempting to call getReader() on a locked stream will throw a TypeError. Always check stream.locked before attempting to acquire a reader.
Does async iteration lock the stream?
Yes, using for await...of to iterate over a stream automatically locks it for the duration of iteration. The lock is released when iteration completes, is interrupted, or encounters an error.
How do I support multiple consumers reading from the same stream?
Use the tee() method to create two independent branches of the stream. Each branch is a separate ReadableStream that can be read independently with its own lock.
What releases a stream lock?
Calling reader.releaseLock() releases the lock. Locks are also automatically released when a stream is closed, canceled, or encounters an error.
Can I transfer a locked stream between contexts?
Yes, streams can be transferred using postMessage(). The lock state is maintained during transfer, allowing the receiving context to continue reading from where the original context left off.
Sources
- MDN Web Docs: ReadableStream locked property - Official documentation covering the basic definition, return value, and code examples
- Node.js Web Streams API Documentation - Comprehensive Node.js-specific documentation including version history and interoperability
- WHATWG Streams Standard - The authoritative specification defining locking behavior, reader/writer models, and state management