Re: [whatwg/streams] Cancelling and exception handling async iteration of ReadableStreams (Issue #1255)

> So yes, you can throw in the loop on abort or some other signal, or you can just call return to silently exit. The problem is that you only get to do this after a new chunk of data has arrive - right?
> So is this a "good" solution?

That's a good point. [As currently specified](https://webidl.spec.whatwg.org/#es-asynchronous-iterator-prototype-object), all calls to `next()` and `return()` are "serialized": if you call `return()` while a previous `next()` promise is still pending, then that `return()` promise is "chained" after the pending `next()` promise.

So unfortunately, it's not possible to cancel a pending read when async-iterating a `ReadableStream`, even though that *would* be possible if you're using a `ReadableStreamDefaultReader` directly.

> I was thinking it would be better to call your fetch with the abortcontroller - that would abort the fetch that is supplying your stream, propagating the abort reason from the underlying source.
> 
> So as a general recommendation I was thinking "abort or cancel the underlying source if mechanisms exist, otherwise you will have to wait for the next loop iteration and call break/return (as you indicate).

Indeed, if you can pass an `AbortSignal` when you construct the `ReadableStream` (like with `fetch()`), then that will be the fastest way to get it to cancel the stream.

However, that does make it harder to compose streams. If you have a pipe chain, ideally you want to consume and cancel it *from the end of the chain*, and then have it propagate up to the start. But now you have to also keep track of an `AbortController` so you can abort it *from the front*... 😕

---

I'm wondering if we should change the specification for this. Should `return()` be allowed to reject all pending `next()` promises immediately, instead of waiting for them to settle? This would align closer to how a "regular" reader works. Even with `readable.values({ preventCancel: true })`, it would work just as `reader.releaseLock()` thanks to #1168. Or does JavaScript have different expectations for how the async iterator protocol is supposed to work? 🤔

Or maybe we could add a `signal` option to [`ReadableStreamIteratorOptions`](https://streams.spec.whatwg.org/#dictdef-readablestreamiteratoroptions)? That would allow the consumer to opt into "faster" cancellation, and it would be usable from `for await..of` loops:
```javascript
async function logChunks(readableXX, { signal }) {
  for await (const chunk of readableXX.values({ signal })) { // <<<
    bytes += chunk.length;
    logConsumer( `Chunk: ${chunk}. Read ${bytes} characters.`);
  }
}
```

-- 
Reply to this email directly or view it on GitHub:
https://github.com/whatwg/streams/issues/1255#issuecomment-1422476634

You are receiving this because you are subscribed to this thread.

Message ID: <whatwg/streams/issues/1255/1422476634@github.com>

Received on Wednesday, 8 February 2023 11:55:40 UTC