> ## Documentation Index
> Fetch the complete documentation index at: https://wreq.sqdsh.win/llms.txt
> Use this file to discover all available pages before exploring further.

# Streaming Responses

> Process large responses incrementally without loading them into memory.

## Streaming basics

The `Response` object supports streaming via the standard `body` property, which returns a `ReadableStream<Uint8Array>`. This allows you to process large responses incrementally.

```typescript theme={null}
import { fetch } from 'wreq-js';

const response = await fetch('https://example.com/large-file', {
  browser: 'chrome_142',
});

const stream = response.body;
if (stream) {
  const reader = stream.getReader();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    console.log(`Received ${value.byteLength} bytes`);
  }
}
```

## Key behaviors

<AccordionGroup>
  <Accordion title="Lazy initialization">
    The `body` property is lazily initialized. It returns `null` for empty responses or a `ReadableStream` for non-empty responses.
  </Accordion>

  <Accordion title="Consumption tracking">
    Accessing `response.body` does **not** mark it as consumed. Only reading from the stream sets `bodyUsed` to `true`.
  </Accordion>

  <Accordion title="Clone before consuming">
    You can call `response.clone()` to create a duplicate response before the body is consumed.
  </Accordion>

  <Accordion title="Single read">
    Once consumed via `json()`, `text()`, `arrayBuffer()`, or stream reading, the body cannot be read again.
  </Accordion>
</AccordionGroup>

## Processing chunks

Process data as it arrives:

```typescript theme={null}
const response = await fetch('https://example.com/stream', {
  browser: 'chrome_142',
});

const reader = response.body?.getReader();
if (!reader) throw new Error('No response body');

const decoder = new TextDecoder();
let result = '';

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  // Decode chunk and process
  const text = decoder.decode(value, { stream: true });
  result += text;
  
  // Process incrementally
  console.log('Chunk:', text.substring(0, 100));
}
```

## Download with progress

Track download progress from the response stream itself:

```typescript theme={null}
const response = await fetch('https://example.com/file.zip', {
  browser: 'chrome_142',
});

const contentLength = response.headers.get('content-length');
const total = contentLength ? parseInt(contentLength, 10) : 0;

const reader = response.body?.getReader();
if (!reader) throw new Error('No response body');

let received = 0;
const chunks: Uint8Array[] = [];

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  chunks.push(value);
  received += value.byteLength;

  if (total) {
    const progress = ((received / total) * 100).toFixed(1);
    console.log(`Progress: ${progress}%`);
  }
}

// Combine chunks into single array
const allChunks = new Uint8Array(received);
let position = 0;
for (const chunk of chunks) {
  allChunks.set(chunk, position);
  position += chunk.byteLength;
}
```

## Transport-level request progress

If you want progress before you start consuming `response.body`, use `onRequestEvent`.
This gives you visibility into request start, headers, and native body download progress.

```typescript theme={null}
const response = await fetch('https://example.com/file.zip', {
  browser: 'chrome_142',
  onRequestEvent(event) {
    switch (event.type) {
      case 'request_start':
        console.log('connecting');
        break;
      case 'request_sent':
        console.log('waiting for response');
        break;
      case 'response_headers':
        console.log('status:', event.status);
        console.log('length:', event.contentLength);
        break;
      case 'body_progress':
        if (event.contentLength) {
          const pct = ((event.downloadedBytes ?? 0) / event.contentLength) * 100;
          console.log(`downloaded ${pct.toFixed(1)}%`);
        }
        break;
      case 'body_complete':
        console.log('download complete');
        break;
    }
  },
});

console.log(await response.arrayBuffer());
```

For more detail on event payloads and diagnostics, see [/guides/request-events](/guides/request-events).

## Server-Sent Events (SSE)

Parse Server-Sent Events from a stream:

```typescript theme={null}
const response = await fetch('https://example.com/events', {
  browser: 'chrome_142',
  headers: { Accept: 'text/event-stream' },
});

const reader = response.body?.getReader();
if (!reader) throw new Error('No response body');

const decoder = new TextDecoder();
let buffer = '';

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  buffer += decoder.decode(value, { stream: true });
  
  // Process complete events
  const events = buffer.split('\n\n');
  buffer = events.pop() || ''; // Keep incomplete event in buffer
  
  for (const event of events) {
    if (event.startsWith('data: ')) {
      const data = event.slice(6);
      console.log('Event:', JSON.parse(data));
    }
  }
}
```

## Related

1. Fetch API response shape: [`fetch()`](/api-reference/fetch)
2. Session concept for multi-step flows: [/concepts/sessions](/concepts/sessions)
