google / google/zx

[Bug]: Unhandled promise rejection in fetch().pipe() when response stream errors

Open
#1,443 3 comments 0 reactions 0 assignees View on GitHub
Dominant language
JavaScript
Stars
45.7k
Forks
1.3k
PR merge metrics
No merged PRs in 30d

Description

### What happened?

The `responseToReadable` function in `goods.ts` assigns an async function to `rs._read`, but doesn't wrap it in a try/catch. If `reader.read()` throws (e.g., network disconnection, aborted request, or corrupted stream), the promise rejection is unhandled and can crash the Node.js process.

```typescript
rs._read = async () => {
const result = await reader.read() // Can throw - unhandled
rs.push(result.done ? null : Buffer.from(result.value))
}
```

### How it should work?

Errors from `reader.read()` should be caught and propagated to the stream via `rs.destroy(err)`, allowing downstream consumers to handle the error gracefully.

### How to reproduce the bug?

```javascript
import { fetch } from 'zx'

// Start a fetch that will be aborted mid-stream
const controller = new AbortController()
const result = fetch('https://speed.hetzner.de/100MB.bin', { signal: controller.signal })

const p = result.pipe`cat`

// Abort after a short delay to trigger the error in _read
setTimeout(() => controller.abort(), 100)

await p // Unhandled promise rejection from _read
```

### Version

8.9.0

### What's OS kind?

All

### What JS runtime is used?

Node.js

### Runtime Version

22.x

### Error stack / relevant log output

```shell
UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch()
```

### Code of Conduct

- [x] I agree to follow this project's Code of Conduct

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.