Repository: denoland/deno
Pull request: fix(ext/web): `ReadableStream.from()` allows `Iterable` instead of `IterableIterator`

`createAsyncFromSyncIterator(x)` which is used in `ReadableStream.from()` expects `x` as `Iterable` but, previous implements specify `Iterator` or `IterableIterator`. If it was `IterableIterator`, it would work, but if it was `Iterator`, an exception will occur.

Tests have been merged into WPT.
https://github.com/web-platform-tests/wpt/pull/46365

Changed files:
filename: ext/web/06_streams.js
patch: @@ -5088,28 +5088,32 @@ function initializeCountSizeFunction(globalObject) {
   WeakMapPrototypeSet(countSizeFunctionWeakMap, globalObject, size);
 }
 
-async function* createAsyncFromSyncIterator(syncIterator) {
-  // deno-lint-ignore prefer-primordials
-  yield* syncIterator;
-}
-
 // Ref: https://tc39.es/ecma262/#sec-getiterator
-function getIterator(obj, async = false) {
-  if (async) {
-    if (obj[SymbolAsyncIterator] == null) {
-      if (obj[SymbolIterator] == null) {
-        throw new TypeError("No iterator found");
-      }
-      return createAsyncFromSyncIterator(obj[SymbolIterator]());
-    } else {
-      return obj[SymbolAsyncIterator]();
+function getAsyncOrSyncIterator(obj) {
+  let iterator;
+  if (obj[SymbolAsyncIterator] != null) {
+    iterator = obj[SymbolAsyncIterator]();
+    if (!isObject(iterator)) {
+      throw new TypeError(
+        "[Symbol.asyncIterator] returned a non-object value",
+      );
     }
-  } else {
-    if (obj[SymbolIterator] == null) {
-      throw new TypeError("No iterator found");
+  } else if (obj[SymbolIterator] != null) {
+    iterator = obj[SymbolIterator]();
+    if (!isObject(iterator)) {
+      throw new TypeError("[Symbol.iterator] returned a non-object value");
     }
-    return obj[SymbolIterator]();
+  } else {
+    throw new TypeError("No iterator found");
+  }
+  if (typeof iterator.next !== "function") {
+    throw new TypeError("iterator.next is not a function");
   }
+  return iterator;
+}
+
+function isObject(x) {
+  return (typeof x === "object" && x != null) || typeof x === "function";
 }
 
 const _resourceBacking = Symbol("[[resourceBacking]]");
@@ -5204,26 +5208,29 @@ class ReadableStream {
     );
     asyncIterable = webidl.converters.any(asyncIterable);
 
-    const iterator = getIterator(asyncIterable, true);
+    const iterator = getAsyncOrSyncIterator(asyncIterable);
 
     const stream = createReadableStream(noop, async () => {
       // deno-lint-ignore prefer-primordials
       const res = await iterator.next();
-      if (typeof res !== "object") {
+      if (!isObject(res)) {
         throw new TypeError("iterator.next value is not an object");
       }
       if (res.done) {
         readableStreamDefaultControllerClose(stream[_controller]);
       } else {
-        readableStreamDefaultControllerEnqueue(stream[_controller], res.value);
+        readableStreamDefaultControllerEnqueue(
+          stream[_controller],
+          await res.value,
+        );
       }
     }, async (reason) => {
-      if (typeof iterator.return === "undefined") {
+      if (iterator.return == null) {
         return undefined;
       } else {
         // deno-lint-ignore prefer-primordials
         const res = await iterator.return(reason);
-        if (typeof res !== "object") {
+        if (!isObject(res)) {
           throw new TypeError("iterator.return value is not an object");
         } else {
           return undefined;

filename: tests/wpt/runner/expectation.json
patch: @@ -3170,14 +3170,8 @@
       "owning-type-message-port.any.worker.html": false,
       "owning-type.any.html": false,
       "owning-type.any.worker.html": false,
-      "from.any.html": [
-        "ReadableStream.from accepts a sync iterable of values",
-        "ReadableStream.from accepts a sync iterable of promises"
-      ],
-      "from.any.worker.html": [
-        "ReadableStream.from accepts a sync iterable of values",
-        "ReadableStream.from accepts a sync iterable of promises"
-      ]
+      "from.any.html": true,
+      "from.any.worker.html": true
     },
     "transform-streams": {
       "backpressure.any.html": true,