JavaScriptAsync/AwaitGeneratorsStreamsPerformance

Beyond Promises: Async Iterators & Generators

S
Streaming Architect
Featured Guide 20 min read

Promises resolve once.

A Promise represents a single future value. But real-world apps process streams of data: WebSocket messages, File Upload chunks, or infinite scrolling feeds.

Enter Async Iterators: A standard way to loop over future events as if they were an array.

02. function* meets await

You can pause execution and return multiple values over time.

// ticker.js
async function*
getStockUpdates(symbol) {'{'}
  
while
(true) {'{'}
    
const
price =
await
fetchPrice(symbol);
    
yield
price;
    
await
sleep(1000);
  {'}'}
{'}'}

// Consumption
for await
(
const
price
of
getStockUpdates('AAPL')) {'{'}
  console.log(price);
{'}'}

03. Real-World Pattern: Paginated APIs

The most common use case isn't stock tickers; it's fetching large datasets. Instead of a recursive function that crashes the stack, use an async generator to "flatten" pages into a single stream of items.

// Flattens pages of users into a single stream
async function* fetchAllUsers() {'{'}
  let page = 1;
  while (true) {'{'}
    // Fetch chunk
    const res = await api.get(`/users?page=${page}`);
    if (res.data.length === 0) break;

    // Yield items one by one
    for (const user of res.data) {'{'}
      yield user;
    {'}'}
    page++;
  {'}'}
{'}'}

// Usage: Looks like a sync loop, behaves async!
for await (const user of fetchAllUsers()) {'{'}
  processUser(user);
{'}'}

Deep Dive: The "Pull" Architecture

This is a Pull Stream. The consumer requests the next item (by calling next() implicit in the loop), and the producer computes it.

This is different from Push Streams (like RxJS Observables or standard DOM Events) where the producer blasts data at you whether you are ready or not. Async Iterators are perfect for flow control because the producer awaits the consumer!

04. The Senior Engineer's Take

Memory Efficiency & Backpressure

Why use this over Promise.all()? Backpressure.

When you process a 1GB file line-by-line using a generator, you only keep one line in memory at a time. A standard file.read() would crash your V8 engine.

Interactive Playground

import React, { useState, useEffect } from 'react';

// 🌊 Stream Visualizer

export default function StreamDemo() {
    const [prices, setPrices] = useState([]);
    const [isRunning, setIsRunning] = useState(false);

    // Mock Async Generator
    async function* stockTicker() {
        let price = 100;
        while (true) {
            await new Promise(r => setTimeout(r, 800)); // Simulate network latency
            price = price + (Math.random() - 0.5) * 5;
            yield price;
        }
    }

    const startStream = async () => {
        if (isRunning) return;
        setIsRunning(true);
        setPrices([]);

        // Consumption Loop
        const iterator = stockTicker();
        // Saving reference to iterator to potentially stop it (in React this logic is tricky inside Effect, 
        // simplified for demo: random limit or component unmount to break)
        
        // Using a ref or flag to break loop in real app
        for await (const price of iterator) {
             setPrices(prev => [...prev.slice(-19), price].filter(Boolean));
             // For demo purposes, we can't easily "break" this loop from outside without an AbortController or Ref check inside.
             // We'll rely on unmounting or page refresh in this simplified code block context.
             // In reality: if (!isRunningRef.current) break;
             if (price > 1000) break; // Infinite safety
        }
    };
    
    // NOTE: The above startStream has no 'stop' mechanism button for simplicity in this sandbox.
    // We will just let it run.

    return (
        <div className="bg-slate-50 dark:bg-slate-950 p-8 rounded-3xl border border-slate-200 dark:border-slate-800 shadow-xl">
             <div className="flex justify-between items-center mb-10">
                <h3 className="text-2xl font-black text-gray-900 dark:text-white flex items-center gap-3">
                    <span className="text-cyan-500">🌊</span> Async Iterator Stream
                </h3>
                <button 
                    onClick={startStream}
                    disabled={isRunning}
                    className="bg-cyan-500 hover:bg-cyan-600 disabled:opacity-50 text-white font-bold px-6 py-2 rounded-xl transition-all flex items-center gap-2"
                >
                    {isRunning ? <span className="animate-pulse">📈</span> : <span>▶️</span>}
                    {isRunning ? 'Streaming...' : 'Start Feed'}
                </button>
            </div>

            <div className="bg-black rounded-xl p-6 h-[300px] relative overflow-hidden flex items-end gap-1">
                 {/* Grid Lines */}
                 <div className="absolute inset-x-0 bottom-10 h-px bg-gray-800"></div>
                 <div className="absolute inset-x-0 bottom-1/2 h-px bg-gray-900"></div>

                 {prices.length === 0 && (
                     <div className="absolute inset-0 flex items-center justify-center text-gray-500">
                         No Data Stream
                     </div>
                 )}

                 {prices.map((p, i) => {
                     const height = (p - 80) * 3; // Normalize roughly
                     return (
                        <div 
                            key={i}
                            className="bg-cyan-500 w-full rounded-t opacity-80 hover:opacity-100 transition-all duration-300"
                            style={{ height: `${Math.max(4, height)}px` }}
                        ></div>
                     );
                 })}
                 
                 <div className="absolute top-4 right-4 font-mono text-cyan-400 font-bold text-2xl">
                     {prices.length > 0 && prices[prices.length-1].toFixed(2)}
                 </div>
            </div>
            
            <div className="mt-4 text-xs text-gray-500 font-mono">
                for await (const price of stream) &#123; render(price) &#125;
            </div>
        </div>
    );
}