- 
      
 - 
        
Save MattiasBuelens/496fc1d37adb50a733edd43853f2f60e to your computer and use it in GitHub Desktop.  
| /** | |
| * A polyfill for `ReadableStream.protototype[Symbol.asyncIterator]`, | |
| * aligning as closely as possible to the specification. | |
| * | |
| * @see https://streams.spec.whatwg.org/#rs-asynciterator | |
| * @see https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream#async_iteration | |
| */ | |
| ReadableStream.prototype.values ??= function({ preventCancel = false } = {}) { | |
| const reader = this.getReader(); | |
| return { | |
| async next() { | |
| try { | |
| const result = await reader.read(); | |
| if (result.done) { | |
| reader.releaseLock(); | |
| } | |
| return result; | |
| } catch (e) { | |
| reader.releaseLock(); | |
| throw e; | |
| } | |
| }, | |
| async return(value) { | |
| if (!preventCancel) { | |
| const cancelPromise = reader.cancel(value); | |
| reader.releaseLock(); | |
| await cancelPromise; | |
| } else { | |
| reader.releaseLock(); | |
| } | |
| return { done: true, value }; | |
| }, | |
| [Symbol.asyncIterator]() { | |
| return this; | |
| } | |
| }; | |
| }; | |
| ReadableStream.prototype[Symbol.asyncIterator] ??= ReadableStream.prototype.values; | 
@HansBrende Hmm, there's something weird.
The WebIDL specification says in step 12 that the async iterator return() method must resolve with { done: true, value: value } (where value is the single parameter passed to return, if any). However, the TypeScript type definitions seem to suggest that if you pass a promise as first parameter, then the method should resolve with the resolved value of that promise. 🤔
I think the TypeScript definitions are wrong. If I try this snippet in Chrome, I get a { done: true, value: Promise } result.
let rs = new ReadableStream();
let it = rs.values();
await it.return(Promise.resolve('foo'));
// -> { done: true, value: Promise {<fulfilled>: 'foo'} }That said, async generators do return the resolved value... 🤔
let gen = (async function*() {})();
await gen.return(Promise.resolve('foo'));
// -> { done: true, value: 'foo' }Uhm, not sure yet. Maybe this is an oversight in the WebIDL specification? 😅
If you just want to fix the types, you can do return { done: true, value: undefined }; or return { done: true, value: await value }; at the end. But keep in mind that this behavior might not be fully spec-compliant!
@MattiasBuelens good info. Just checked node and firefox to be sure, and the behavior is consistent with chrome:
node -e "let rs = new ReadableStream(); let it = rs.values(); it.return(Promise.resolve('foo')).then(x => console.log(x));"
{ done: true, value: Promise { 'foo' } }
That's so weird though that the behavior would be inconsistent with async generators... feel like this has to be an oversight somewhere.
@MattiasBuelens important note though: it does not appear as though the incoming promise is awaited:
node -e "let rs = new ReadableStream(); let it = rs.values(); it.return(new Promise(setTimeout)).then(console.log);"
{ done: true, value: Promise { <pending> } }
Given that information, the best typescript fix to align with existing behavior seems to be:
return { done: true, value: value as undefined };I've subsequently posted an issue in the WebIDL github to confirm whether this behavior is intentional or an oversight: whatwg/webidl#1522.


@MattiasBuelens when I try this in typescript, it complains about the return type of
async return(value), saying that the incomingvalueis of typePromiseLike<undefined> | undefined, whereas the expected return type is{ done: true, value: undefined}.So is this polyfill correct in that respect? Or should it have
return { done: true, value: undefined };at the end?