Problem: Asynchronous Streaming Adapter
Problem statement
You maintain a legacy file reader utility that processes data in chunks. It uses a callback pattern to push each chunk of data, as shown:
legacyReader.readFile('log.txt', chunk => { ... });
Modern code in your app expects an async iterator that it can loop over using:
for await (const chunk of reader) { ... }
You need to write an adapter that makes the legacy callback-based reader compatible with this newer iteration model—without modifying the original code.
Goal
Implement a StreamAdapter class that wraps a legacy reader and exposes an async iterable interface.
for await (const chunk of new StreamAdapter(legacyReader, 'file.txt')) { ... }
The adapter should:
Use an internal queue to store incoming chunks.
Resolve iteration as chunks arrive.
End gracefully when the legacy reader signals completion.
Constraints
You cannot modify the legacy reader.
The adapter must implement the
[Symbol.asyncIterator]()protocol.It must work seamlessly inside
for await...of.Assume the legacy reader calls the callback with
nullwhen the stream ends.
Sample output
The examples below illustrate what the output should look like:
(async () => {const stream = new StreamAdapter(legacyReader, 'log.txt');for await (const chunk of stream) {console.log('Chunk:', chunk);}console.log('Done reading.');})();/* Expected output:Chunk: [2025-10-10 10:00:12] INFO Server started on port 3000[2025-10-10 10:00:14] INFO Connected to database successfully[2025-10-10 10:00:18] WARN High memory usage detected[2025-10-10 10:00:22] ERROR Failed to fetch user profile[2025-10-10 10:00:25] INFO Request completed in 213msDone reading.*/
Good luck trying the problem! If you’re unsure how to proceed, check the “Solution” tab above.
Understanding the async iterator protocol
A quick example
Why this matters
Problem: Asynchronous Streaming Adapter
Problem statement
You maintain a legacy file reader utility that processes data in chunks. It uses a callback pattern to push each chunk of data, as shown:
legacyReader.readFile('log.txt', chunk => { ... });
Modern code in your app expects an async iterator that it can loop over using:
for await (const chunk of reader) { ... }
You need to write an adapter that makes the legacy callback-based reader compatible with this newer iteration model—without modifying the original code.
Goal
Implement a StreamAdapter class that wraps a legacy reader and exposes an async iterable interface.
for await (const chunk of new StreamAdapter(legacyReader, 'file.txt')) { ... }
The adapter should:
Use an internal queue to store incoming chunks.
Resolve iteration as chunks arrive.
End gracefully when the legacy reader signals completion.
Constraints
You cannot modify the legacy reader.
The adapter must implement the
[Symbol.asyncIterator]()protocol.It must work seamlessly inside
for await...of.Assume the legacy reader calls the callback with
nullwhen the stream ends.
Sample output
The examples below illustrate what the output should look like:
(async () => {const stream = new StreamAdapter(legacyReader, 'log.txt');for await (const chunk of stream) {console.log('Chunk:', chunk);}console.log('Done reading.');})();/* Expected output:Chunk: [2025-10-10 10:00:12] INFO Server started on port 3000[2025-10-10 10:00:14] INFO Connected to database successfully[2025-10-10 10:00:18] WARN High memory usage detected[2025-10-10 10:00:22] ERROR Failed to fetch user profile[2025-10-10 10:00:25] INFO Request completed in 213msDone reading.*/
Good luck trying the problem! If you’re unsure how to proceed, check the “Solution” tab above.