Skip to content

Commit 7ffbb95

Browse files
Merge pull request #144 from MattiasBuelens/optimize-rs-from
Optimize `ReadableStream.from()`
2 parents 0616eb6 + 93d28a1 commit 7ffbb95

File tree

4 files changed

+69
-36
lines changed

4 files changed

+69
-36
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
* When using TypeScript, version 4.7 or higher is now required. Additionally, [`moduleResolution`](https://www.typescriptlang.org/tsconfig#moduleResolution) must be set to `"node16"`, `"nodenext"` or `"bundler"`.
2323
* 🚀 Support [importing as ESM in Node](https://nodejs.org/api/esm.html).
2424
* 💅 Minify all code in the published package, to reduce the download size.
25+
* 💅 Rework `ReadableStream.from()` implementation to avoid depending on `async function*` down-leveling for ES5. ([#144](https://github.com/MattiasBuelens/web-streams-polyfill/pull/144))
2526

2627
| v3 import | v4 import | description |
2728
| --- | --- | --- |

src/lib/abstract-ops/ecmascript.ts

Lines changed: 60 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { reflectCall } from 'lib/helpers/webidl';
1+
import {
2+
PerformPromiseThen,
3+
promiseRejectedWith,
4+
promiseResolve,
5+
promiseResolvedWith,
6+
reflectCall
7+
} from 'lib/helpers/webidl';
28
import { typeIsObject } from '../helpers/miscellaneous';
39
import assert from '../../stub/assert';
410

@@ -79,9 +85,11 @@ export function GetMethod<T, K extends MethodName<T>>(receiver: T, prop: K): T[K
7985
return func;
8086
}
8187

88+
export type SyncOrAsync<T> = T | Promise<T>;
89+
8290
export interface SyncIteratorRecord<T> {
8391
iterator: Iterator<T>,
84-
nextMethod: Iterator<T>['next'],
92+
nextMethod: () => SyncOrAsync<IteratorResult<SyncOrAsync<T>>>,
8593
done: boolean;
8694
}
8795

@@ -93,23 +101,57 @@ export interface AsyncIteratorRecord<T> {
93101

94102
export type SyncOrAsyncIteratorRecord<T> = SyncIteratorRecord<T> | AsyncIteratorRecord<T>;
95103

96-
export function CreateAsyncFromSyncIterator<T>(syncIteratorRecord: SyncIteratorRecord<T>): AsyncIteratorRecord<T> {
97-
// Instead of re-implementing CreateAsyncFromSyncIterator and %AsyncFromSyncIteratorPrototype%,
98-
// we use yield* inside an async generator function to achieve the same result.
99-
100-
// Wrap the sync iterator inside a sync iterable, so we can use it with yield*.
101-
const syncIterable = {
102-
[Symbol.iterator]: () => syncIteratorRecord.iterator
104+
export function CreateAsyncFromSyncIterator<T>(
105+
syncIteratorRecord: SyncIteratorRecord<SyncOrAsync<T>>
106+
): AsyncIteratorRecord<T> {
107+
const asyncIterator: AsyncIterator<T> = {
108+
// https://tc39.es/ecma262/#sec-%asyncfromsynciteratorprototype%.next
109+
next() {
110+
let result;
111+
try {
112+
result = IteratorNext(syncIteratorRecord);
113+
} catch (e) {
114+
return promiseRejectedWith(e);
115+
}
116+
return AsyncFromSyncIteratorContinuation(result);
117+
},
118+
// https://tc39.es/ecma262/#sec-%asyncfromsynciteratorprototype%.return
119+
return(value: any) {
120+
let result;
121+
try {
122+
const returnMethod = GetMethod(syncIteratorRecord.iterator, 'return');
123+
if (returnMethod === undefined) {
124+
return promiseResolvedWith({ done: true, value });
125+
}
126+
// Note: ReadableStream.from() always calls return() with a value.
127+
result = reflectCall(returnMethod, syncIteratorRecord.iterator, [value]);
128+
} catch (e) {
129+
return promiseRejectedWith(e);
130+
}
131+
if (!typeIsObject(result)) {
132+
return promiseRejectedWith(new TypeError('The iterator.return() method must return an object'));
133+
}
134+
return AsyncFromSyncIteratorContinuation(result);
135+
}
136+
// Note: throw() is never used by the Streams spec.
103137
};
104-
// Create an async generator function and immediately invoke it.
105-
const asyncIterator = (async function* () {
106-
return yield* syncIterable;
107-
}());
108138
// Return as an async iterator record.
109139
const nextMethod = asyncIterator.next;
110140
return { iterator: asyncIterator, nextMethod, done: false };
111141
}
112142

143+
// https://tc39.es/ecma262/#sec-asyncfromsynciteratorcontinuation
144+
function AsyncFromSyncIteratorContinuation<T>(result: IteratorResult<SyncOrAsync<T>>): Promise<IteratorResult<T>> {
145+
try {
146+
const done = result.done;
147+
const value = result.value;
148+
const valueWrapper = promiseResolve(value);
149+
return PerformPromiseThen(valueWrapper, v => ({ done, value: v }));
150+
} catch (e) {
151+
return promiseRejectedWith(e);
152+
}
153+
}
154+
113155
// Aligns with core-js/modules/es.symbol.async-iterator.js
114156
export const SymbolAsyncIterator: (typeof Symbol)['asyncIterator'] =
115157
Symbol.asyncIterator ??
@@ -160,22 +202,15 @@ function GetIterator<T>(
160202

161203
export { GetIterator };
162204

163-
export function IteratorNext<T>(iteratorRecord: AsyncIteratorRecord<T>): Promise<IteratorResult<T>> {
205+
export function IteratorNext<T>(iteratorRecord: SyncIteratorRecord<T>): IteratorResult<T>;
206+
export function IteratorNext<T>(iteratorRecord: AsyncIteratorRecord<T>): Promise<IteratorResult<T>>;
207+
export function IteratorNext<T>(
208+
iteratorRecord: SyncOrAsyncIteratorRecord<T>
209+
): SyncOrAsync<IteratorResult<SyncOrAsync<T>>> {
164210
const result = reflectCall(iteratorRecord.nextMethod, iteratorRecord.iterator, []);
165211
if (!typeIsObject(result)) {
166212
throw new TypeError('The iterator.next() method must return an object');
167213
}
168214
return result;
169215
}
170216

171-
export function IteratorComplete<TReturn>(
172-
iterResult: IteratorResult<unknown, TReturn>
173-
): iterResult is IteratorReturnResult<TReturn> {
174-
assert(typeIsObject(iterResult));
175-
return Boolean(iterResult.done);
176-
}
177-
178-
export function IteratorValue<T>(iterResult: IteratorYieldResult<T>): T {
179-
assert(typeIsObject(iterResult));
180-
return iterResult.value;
181-
}

src/lib/helpers/webidl.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ import { rethrowAssertionErrorRejection } from './miscellaneous';
22
import assert from '../../stub/assert';
33

44
const originalPromise = Promise;
5+
const originalPromiseResolve = Promise.resolve.bind(originalPromise);
56
const originalPromiseThen = Promise.prototype.then;
67
const originalPromiseReject = Promise.reject.bind(originalPromise);
78

9+
export const promiseResolve = originalPromiseResolve;
10+
811
// https://webidl.spec.whatwg.org/#a-new-promise
912
export function newPromise<T>(executor: (
1013
resolve: (value: T | PromiseLike<T>) => void,

src/lib/readable-stream/from.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import {
55
type ReadableStreamLike
66
} from './readable-stream-like';
77
import { ReadableStreamDefaultControllerClose, ReadableStreamDefaultControllerEnqueue } from './default-controller';
8-
import { GetIterator, GetMethod, IteratorComplete, IteratorNext, IteratorValue } from '../abstract-ops/ecmascript';
9-
import { promiseRejectedWith, promiseResolvedWith, reflectCall, transformPromiseWith } from '../helpers/webidl';
8+
import { GetIterator, GetMethod, IteratorNext } from '../abstract-ops/ecmascript';
9+
import { promiseCall, promiseRejectedWith, promiseResolvedWith, transformPromiseWith } from '../helpers/webidl';
1010
import { typeIsObject } from '../helpers/miscellaneous';
1111
import { noop } from '../../utils';
1212

@@ -37,11 +37,11 @@ export function ReadableStreamFromIterable<R>(asyncIterable: Iterable<R> | Async
3737
if (!typeIsObject(iterResult)) {
3838
throw new TypeError('The promise returned by the iterator.next() method must fulfill with an object');
3939
}
40-
const done = IteratorComplete(iterResult);
40+
const done = iterResult.done;
4141
if (done) {
4242
ReadableStreamDefaultControllerClose(stream._readableStreamController);
4343
} else {
44-
const value = IteratorValue(iterResult);
44+
const value = iterResult.value;
4545
ReadableStreamDefaultControllerEnqueue(stream._readableStreamController, value);
4646
}
4747
});
@@ -58,13 +58,7 @@ export function ReadableStreamFromIterable<R>(asyncIterable: Iterable<R> | Async
5858
if (returnMethod === undefined) {
5959
return promiseResolvedWith(undefined);
6060
}
61-
let returnResult: IteratorResult<R> | Promise<IteratorResult<R>>;
62-
try {
63-
returnResult = reflectCall(returnMethod, iterator, [reason]);
64-
} catch (e) {
65-
return promiseRejectedWith(e);
66-
}
67-
const returnPromise = promiseResolvedWith(returnResult);
61+
const returnPromise = promiseCall(returnMethod, iterator, [reason]);
6862
return transformPromiseWith(returnPromise, iterResult => {
6963
if (!typeIsObject(iterResult)) {
7064
throw new TypeError('The promise returned by the iterator.return() method must fulfill with an object');

0 commit comments

Comments
 (0)