Skip to content

Commit

Permalink
Implement CreateAsyncFromSyncIterator manually
Browse files Browse the repository at this point in the history
  • Loading branch information
MattiasBuelens committed Feb 28, 2024
1 parent a5d1830 commit fc67d16
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 14 deletions.
74 changes: 60 additions & 14 deletions src/lib/abstract-ops/ecmascript.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { reflectCall } from 'lib/helpers/webidl';
import {
PerformPromiseThen,
promiseRejectedWith,
promiseResolve,
promiseResolvedWith,
reflectCall
} from 'lib/helpers/webidl';
import { typeIsObject } from '../helpers/miscellaneous';
import assert from '../../stub/assert';

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

export type SyncOrAsync<T> = T | Promise<T>;

export interface SyncIteratorRecord<T> {
iterator: Iterator<T>,
nextMethod: Iterator<T>['next'],
nextMethod: () => SyncOrAsync<IteratorResult<SyncOrAsync<T>>>,
done: boolean;
}

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

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

export function CreateAsyncFromSyncIterator<T>(syncIteratorRecord: SyncIteratorRecord<T>): AsyncIteratorRecord<T> {
// Instead of re-implementing CreateAsyncFromSyncIterator and %AsyncFromSyncIteratorPrototype%,
// we use yield* inside an async generator function to achieve the same result.

// Wrap the sync iterator inside a sync iterable, so we can use it with yield*.
const syncIterable = {
[Symbol.iterator]: () => syncIteratorRecord.iterator
export function CreateAsyncFromSyncIterator<T>(
syncIteratorRecord: SyncIteratorRecord<SyncOrAsync<T>>
): AsyncIteratorRecord<T> {
const asyncIterator: AsyncIterator<T> = {
// https://tc39.es/ecma262/#sec-%asyncfromsynciteratorprototype%.next
next() {
let result;
try {
result = IteratorNext(syncIteratorRecord);
} catch (e) {
return promiseRejectedWith(e);
}
return AsyncFromSyncIteratorContinuation(result);
},
// https://tc39.es/ecma262/#sec-%asyncfromsynciteratorprototype%.return
return(value: any) {
let result;
try {
const returnMethod = GetMethod(syncIteratorRecord.iterator, 'return');
if (returnMethod === undefined) {
return promiseResolvedWith({ done: true, value });
}
// Note: ReadableStream.from() always calls return() with a value.
result = reflectCall(returnMethod, syncIteratorRecord.iterator, [value]);
} catch (e) {
return promiseRejectedWith(e);
}
if (!typeIsObject(result)) {
return promiseRejectedWith(new TypeError('The iterator.return() method must return an object'));
}
return AsyncFromSyncIteratorContinuation(result);
}
// Note: throw() is never used by the Streams spec.
};
// Create an async generator function and immediately invoke it.
const asyncIterator = (async function* () {
return yield* syncIterable;
}());
// Return as an async iterator record.
const nextMethod = asyncIterator.next;
return { iterator: asyncIterator, nextMethod, done: false };
}

// https://tc39.es/ecma262/#sec-asyncfromsynciteratorcontinuation
function AsyncFromSyncIteratorContinuation<T>(result: IteratorResult<SyncOrAsync<T>>): Promise<IteratorResult<T>> {
try {
const done = result.done;
const value = result.value;
const valueWrapper = promiseResolve(value);
return PerformPromiseThen(valueWrapper, v => ({ done, value: v }));
} catch (e) {
return promiseRejectedWith(e);
}
}

// Aligns with core-js/modules/es.symbol.async-iterator.js
export const SymbolAsyncIterator: (typeof Symbol)['asyncIterator'] =
Symbol.asyncIterator ??
Expand Down Expand Up @@ -160,7 +202,11 @@ function GetIterator<T>(

export { GetIterator };

export function IteratorNext<T>(iteratorRecord: AsyncIteratorRecord<T>): Promise<IteratorResult<T>> {
export function IteratorNext<T>(iteratorRecord: SyncIteratorRecord<T>): IteratorResult<T>;
export function IteratorNext<T>(iteratorRecord: AsyncIteratorRecord<T>): Promise<IteratorResult<T>>;
export function IteratorNext<T>(
iteratorRecord: SyncOrAsyncIteratorRecord<T>
): SyncOrAsync<IteratorResult<SyncOrAsync<T>>> {
const result = reflectCall(iteratorRecord.nextMethod, iteratorRecord.iterator, []);
if (!typeIsObject(result)) {
throw new TypeError('The iterator.next() method must return an object');
Expand Down
3 changes: 3 additions & 0 deletions src/lib/helpers/webidl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import { rethrowAssertionErrorRejection } from './miscellaneous';
import assert from '../../stub/assert';

const originalPromise = Promise;
const originalPromiseResolve = Promise.resolve.bind(originalPromise);
const originalPromiseThen = Promise.prototype.then;
const originalPromiseReject = Promise.reject.bind(originalPromise);

export const promiseResolve = originalPromiseResolve;

// https://webidl.spec.whatwg.org/#a-new-promise
export function newPromise<T>(executor: (
resolve: (value: T | PromiseLike<T>) => void,
Expand Down

0 comments on commit fc67d16

Please sign in to comment.