From fc67d1626e9e020b36236a0b63702d64b4eee1b9 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Sun, 14 Jan 2024 22:51:26 +0100 Subject: [PATCH] Implement CreateAsyncFromSyncIterator manually --- src/lib/abstract-ops/ecmascript.ts | 74 ++++++++++++++++++++++++------ src/lib/helpers/webidl.ts | 3 ++ 2 files changed, 63 insertions(+), 14 deletions(-) diff --git a/src/lib/abstract-ops/ecmascript.ts b/src/lib/abstract-ops/ecmascript.ts index 1416fbf..3bac163 100644 --- a/src/lib/abstract-ops/ecmascript.ts +++ b/src/lib/abstract-ops/ecmascript.ts @@ -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'; @@ -79,9 +85,11 @@ export function GetMethod>(receiver: T, prop: K): T[K return func; } +export type SyncOrAsync = T | Promise; + export interface SyncIteratorRecord { iterator: Iterator, - nextMethod: Iterator['next'], + nextMethod: () => SyncOrAsync>>, done: boolean; } @@ -93,23 +101,57 @@ export interface AsyncIteratorRecord { export type SyncOrAsyncIteratorRecord = SyncIteratorRecord | AsyncIteratorRecord; -export function CreateAsyncFromSyncIterator(syncIteratorRecord: SyncIteratorRecord): AsyncIteratorRecord { - // 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( + syncIteratorRecord: SyncIteratorRecord> +): AsyncIteratorRecord { + const asyncIterator: AsyncIterator = { + // 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(result: IteratorResult>): Promise> { + 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 ?? @@ -160,7 +202,11 @@ function GetIterator( export { GetIterator }; -export function IteratorNext(iteratorRecord: AsyncIteratorRecord): Promise> { +export function IteratorNext(iteratorRecord: SyncIteratorRecord): IteratorResult; +export function IteratorNext(iteratorRecord: AsyncIteratorRecord): Promise>; +export function IteratorNext( + iteratorRecord: SyncOrAsyncIteratorRecord +): SyncOrAsync>> { const result = reflectCall(iteratorRecord.nextMethod, iteratorRecord.iterator, []); if (!typeIsObject(result)) { throw new TypeError('The iterator.next() method must return an object'); diff --git a/src/lib/helpers/webidl.ts b/src/lib/helpers/webidl.ts index 425e837..4bc106c 100644 --- a/src/lib/helpers/webidl.ts +++ b/src/lib/helpers/webidl.ts @@ -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(executor: ( resolve: (value: T | PromiseLike) => void,