@@ -173,6 +173,15 @@ class AsyncGenerator final {
173173 }
174174 }
175175
176+ // Returns true if the 'return' property exists on the iterator but is not callable.
177+ // Per GetMethod spec, this should result in a TypeError when return_() is called.
178+ bool hasNonCallableReturn () const {
179+ KJ_IF_SOME (active, maybeActive) {
180+ return active.returnExistsButNotCallable ;
181+ }
182+ return false ;
183+ }
184+
176185 // If nothing is returned, the generator is complete.
177186 Promise<kj::Maybe<T>> next (Lock& js) {
178187 KJ_IF_SOME (active, maybeActive) {
@@ -205,7 +214,10 @@ class AsyncGenerator final {
205214 }
206215
207216 // If nothing is returned, the generator is complete.
208- Promise<kj::Maybe<T>> return_ (Lock& js, kj::Maybe<T> maybeValue = kj::none) {
217+ // When propagateException is true, rejections from return() are propagated directly
218+ // without calling throw_(). This matches spec behavior for ReadableStream.from.
219+ Promise<kj::Maybe<T>> return_ (
220+ Lock& js, kj::Maybe<T> maybeValue = kj::none, bool propagateException = false ) {
209221 KJ_IF_SOME (active, maybeActive) {
210222 KJ_IF_SOME (return_, active.maybeReturn ) {
211223 auto & selfRef = KJ_ASSERT_NONNULL (maybeSelfRef);
@@ -216,17 +228,21 @@ class AsyncGenerator final {
216228 ref->runIfAlive ([&](AsyncGenerator& self) { self.maybeActive = kj::none; });
217229 }
218230 return js.resolvedPromise (kj::mv (result.value ));
219- }, [ref = selfRef.addRef ()](Lock& js, Value exception) {
231+ }, [ref = selfRef.addRef (), propagateException ](Lock& js, Value exception) {
220232 Promise<kj::Maybe<T>> retPromise = nullptr ;
221- if (ref->runIfAlive ([&](AsyncGenerator& self) {
233+ if (!propagateException && ref->runIfAlive ([&](AsyncGenerator& self) {
222234 retPromise = self.throw_ (js, kj::mv (exception));
223235 })) {
224236 return kj::mv (retPromise);
225237 }
238+ ref->runIfAlive ([&](AsyncGenerator& self) { self.maybeActive = kj::none; });
226239 return js.rejectedPromise <kj::Maybe<T>>(kj::mv (exception));
227240 });
228241 }, [&](Value exception) {
229242 maybeActive = kj::none;
243+ if (propagateException) {
244+ return js.rejectedPromise <kj::Maybe<T>>(kj::mv (exception));
245+ }
230246 return throw_ (js, kj::mv (exception));
231247 });
232248 }
@@ -276,13 +292,19 @@ class AsyncGenerator final {
276292 kj::Maybe<NextSignature> maybeNext;
277293 kj::Maybe<ReturnSignature> maybeReturn;
278294 kj::Maybe<ThrowSignature> maybeThrow;
295+ // Per GetMethod spec, if property exists but is not callable, we should throw TypeError.
296+ // We track this state to defer the error to when return_() is actually called.
297+ bool returnExistsButNotCallable = false ;
279298
280299 template <typename TypeWrapper>
281300 Active (Lock& js, JsObject object, TypeWrapper*)
282301 : maybeNext(tryGetGeneratorFunction<NextSignature, TypeWrapper>(js, object, " next" _kj)),
283302 maybeReturn (
284303 tryGetGeneratorFunction<ReturnSignature, TypeWrapper>(js, object, " return" _kj)),
285304 maybeThrow(tryGetGeneratorFunction<ThrowSignature, TypeWrapper>(js, object, " throw" _kj)) {
305+ // Check if return property exists but isn't callable (per GetMethod spec)
306+ returnExistsButNotCallable =
307+ maybeReturn == kj::none && !object.get (js, " return" _kj).isNullOrUndefined ();
286308 }
287309 Active (Active&&) = default;
288310 Active& operator =(Active&&) = default ;
0 commit comments