Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make ReadableStream properly implement AsyncIterable #142

Merged
merged 6 commits into from
Jan 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
> - 🏠 Internal
> - 💅 Polish

## Unreleased

* 🐛 Fix `ReadableStream` to match TypeScript's `AsyncIterable<R>` type. ([#141](https://github.com/MattiasBuelens/web-streams-polyfill/issues/141), [#142](https://github.com/MattiasBuelens/web-streams-polyfill/pull/142))

## 3.3.2 (2024-01-04)

* 🐛 Fix bad publish to npm.
Expand Down
5 changes: 2 additions & 3 deletions dist/types/ts3.6/polyfill.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { ReadableStreamAsyncIterator, ReadableStreamIteratorOptions } from
export * from './ponyfill';

declare global {
interface ReadableStream<R = any> {
interface ReadableStream<R = any> extends AsyncIterable<R> {
/**
* Asynchronously iterates over the chunks in the stream's internal queue.
*
Expand All @@ -23,7 +23,6 @@ declare global {
/**
* {@inheritDoc ReadableStream.values}
*/
[Symbol.asyncIterator]: (options?: ReadableStreamIteratorOptions) => ReadableStreamAsyncIterator<R>;
[Symbol.asyncIterator](options?: ReadableStreamIteratorOptions): ReadableStreamAsyncIterator<R>;
}
}

6 changes: 3 additions & 3 deletions etc/web-streams-polyfill.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ export class ReadableByteStreamController {
}

// @public
export class ReadableStream<R = any> {
[Symbol.asyncIterator]: (options?: ReadableStreamIteratorOptions) => ReadableStreamAsyncIterator<R>;
export class ReadableStream<R = any> implements AsyncIterable<R> {
[Symbol.asyncIterator](options?: ReadableStreamIteratorOptions): ReadableStreamAsyncIterator<R>;
constructor(underlyingSource: UnderlyingByteSource, strategy?: {
highWaterMark?: number;
size?: undefined;
Expand All @@ -76,7 +76,7 @@ export class ReadableStream<R = any> {
}

// @public
export interface ReadableStreamAsyncIterator<R> extends AsyncIterator<R> {
export interface ReadableStreamAsyncIterator<R> extends AsyncIterableIterator<R> {
// (undocumented)
next(): Promise<IteratorResult<R, undefined>>;
// (undocumented)
Expand Down
4 changes: 2 additions & 2 deletions rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,13 @@ function bundle(entry, { esm = false, minify = false, target = 'es5' } = {}) {
declaration: false,
declarationMap: false
}),
inject({
target === 'es5' ? inject({
include: 'src/**/*.ts',
exclude: 'src/stub/symbol.ts',
modules: {
Symbol: path.resolve(dirname, './src/stub/symbol.ts')
}
}),
}) : undefined,
replace({
include: 'src/**/*.ts',
preventAssignment: true,
Expand Down
8 changes: 7 additions & 1 deletion src/lib/abstract-ops/ecmascript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ export function CreateAsyncFromSyncIterator<T>(syncIteratorRecord: SyncIteratorR
return { iterator: asyncIterator, nextMethod, done: false };
}

// Aligns with core-js/modules/es.symbol.async-iterator.js
export const SymbolAsyncIterator: (typeof Symbol)['asyncIterator'] =
Symbol.asyncIterator ??
Symbol.for?.('Symbol.asyncIterator') ??
'@@asyncIterator';

export type SyncOrAsyncIterable<T> = Iterable<T> | AsyncIterable<T>;
export type SyncOrAsyncIteratorMethod<T> = () => (Iterator<T> | AsyncIterator<T>);

Expand All @@ -131,7 +137,7 @@ function GetIterator<T>(
assert(hint === 'sync' || hint === 'async');
if (method === undefined) {
if (hint === 'async') {
method = GetMethod(obj as AsyncIterable<T>, Symbol.asyncIterator);
method = GetMethod(obj as AsyncIterable<T>, SymbolAsyncIterator);
if (method === undefined) {
const syncMethod = GetMethod(obj as Iterable<T>, Symbol.iterator);
const syncIteratorRecord = GetIterator(obj as Iterable<T>, 'sync', syncMethod);
Expand Down
23 changes: 13 additions & 10 deletions src/lib/readable-stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import type {
} from './readable-stream/underlying-source';
import { noop } from '../utils';
import { setFunctionName, typeIsObject } from './helpers/miscellaneous';
import { CreateArrayFromList } from './abstract-ops/ecmascript';
import { CreateArrayFromList, SymbolAsyncIterator } from './abstract-ops/ecmascript';
import { CancelSteps } from './abstract-ops/internal-methods';
import { IsNonNegativeNumber } from './abstract-ops/miscellaneous';
import { assertObject, assertRequiredArgument } from './validators/basic';
Expand Down Expand Up @@ -85,7 +85,7 @@ type ReadableStreamState = 'readable' | 'closed' | 'errored';
*
* @public
*/
export class ReadableStream<R = any> {
export class ReadableStream<R = any> implements AsyncIterable<R> {
/** @internal */
_state!: ReadableStreamState;
/** @internal */
Expand Down Expand Up @@ -329,7 +329,12 @@ export class ReadableStream<R = any> {
/**
* {@inheritDoc ReadableStream.values}
*/
[Symbol.asyncIterator]!: (options?: ReadableStreamIteratorOptions) => ReadableStreamAsyncIterator<R>;
[Symbol.asyncIterator](options?: ReadableStreamIteratorOptions): ReadableStreamAsyncIterator<R>;

[SymbolAsyncIterator](options?: ReadableStreamIteratorOptions): ReadableStreamAsyncIterator<R> {
// Stub implementation, overridden below
return this.values(options);
}

/**
* Creates a new ReadableStream wrapping the provided iterable or async iterable.
Expand Down Expand Up @@ -367,13 +372,11 @@ if (typeof Symbol.toStringTag === 'symbol') {
configurable: true
});
}
if (typeof Symbol.asyncIterator === 'symbol') {
Object.defineProperty(ReadableStream.prototype, Symbol.asyncIterator, {
value: ReadableStream.prototype.values,
writable: true,
configurable: true
});
}
Object.defineProperty(ReadableStream.prototype, SymbolAsyncIterator, {
value: ReadableStream.prototype.values,
writable: true,
configurable: true
});

export type {
ReadableStreamAsyncIterator,
Expand Down
6 changes: 2 additions & 4 deletions src/lib/readable-stream/async-iterator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
*
* @public
*/
export interface ReadableStreamAsyncIterator<R> extends AsyncIterator<R> {
export interface ReadableStreamAsyncIterator<R> extends AsyncIterableIterator<R> {
next(): Promise<IteratorResult<R, undefined>>;

return(value?: any): Promise<IteratorResult<any>>;
Expand Down Expand Up @@ -140,9 +140,7 @@ const ReadableStreamAsyncIteratorPrototype: ReadableStreamAsyncIteratorInstance<
return this._asyncIteratorImpl.return(value);
}
} as any;
if (AsyncIteratorPrototype !== undefined) {
Object.setPrototypeOf(ReadableStreamAsyncIteratorPrototype, AsyncIteratorPrototype);
}
Object.setPrototypeOf(ReadableStreamAsyncIteratorPrototype, AsyncIteratorPrototype);

// Abstract operations for the ReadableStream.

Expand Down
2 changes: 1 addition & 1 deletion src/target/es2018/stub/async-iterator-prototype.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// <reference lib="es2018.asynciterable" />

/* eslint-disable @typescript-eslint/no-empty-function */
export const AsyncIteratorPrototype: AsyncIterable<any> | undefined =
export const AsyncIteratorPrototype: AsyncIterable<any> =
Object.getPrototypeOf(Object.getPrototypeOf(async function* (): AsyncIterableIterator<any> {}).prototype);
23 changes: 10 additions & 13 deletions src/target/es5/stub/async-iterator-prototype.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
/// <reference lib="es2018.asynciterable" />

export let AsyncIteratorPrototype: AsyncIterable<any> | undefined;
import { SymbolAsyncIterator } from '../../../lib/abstract-ops/ecmascript';

if (typeof Symbol.asyncIterator === 'symbol') {
// We're running inside a ES2018+ environment, but we're compiling to an older syntax.
// We cannot access %AsyncIteratorPrototype% without non-ES2018 syntax, but we can re-create it.
AsyncIteratorPrototype = {
// 25.1.3.1 %AsyncIteratorPrototype% [ @@asyncIterator ] ( )
// https://tc39.github.io/ecma262/#sec-asynciteratorprototype-asynciterator
[Symbol.asyncIterator](this: AsyncIterator<any>) {
return this;
}
};
Object.defineProperty(AsyncIteratorPrototype, Symbol.asyncIterator, { enumerable: false });
}
// We cannot access %AsyncIteratorPrototype% without non-ES2018 syntax, but we can re-create it.
export const AsyncIteratorPrototype: AsyncIterable<any> = {
// 25.1.3.1 %AsyncIteratorPrototype% [ @@asyncIterator ] ( )
// https://tc39.github.io/ecma262/#sec-asynciteratorprototype-asynciterator
[SymbolAsyncIterator](this: AsyncIterator<any>) {
return this;
}
};
Object.defineProperty(AsyncIteratorPrototype, SymbolAsyncIterator, { enumerable: false });