From 30d6630e5e15cd93c6838c56a1d9ba7da1ea52bb Mon Sep 17 00:00:00 2001 From: Marco Castelluccio Date: Thu, 26 Sep 2024 00:21:30 +0000 Subject: [PATCH] Bug 1920020 [wpt PR 48280] - DOM: Implement abortable async iterable Observables, a=testonly Automatic update from web-platform-tests DOM: Implement abortable async iterable Observables The IteratorRecord#return() function exists as an optional method that sync and async iterator records can supply [1] [2]. They allow for the language, or any consumer of an iterable, to signal to the iterable that the consumer will stop consuming values prematurely (i.e., before exhaustion). This method must be invoked when the consumer aborts its subscription to an Observable that was derived from an iterable. The abort reason is supplied to the `return()` iterator function for completeness. This CL: 1. Adds tests for sync & async iterables 2. Implements this for async iterables A follow-up CL will implement this for sync iterables. The semantics are specified in https://github.com/WICG/observable/pull/160. [1]: https://tc39.es/ecma262/#table-iterator-interface-optional-properties [2]: https://tc39.es/ecma262/#table-async-iterator-optional Bug: 40282760 Change-Id: Ie1091b24b233afecdec572feadc129bcc8a2d4d3 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5854985 Reviewed-by: Mason Freed Commit-Queue: Dominic Farolino Reviewed-by: Nate Chapin Cr-Commit-Position: refs/heads/main{#1359083} -- wpt-commits: 83154d0455e572de16e84ebee72f56df73d2ceb3 wpt-pr: 48280 UltraBlame original commit: 3bc676bcae7a77bead1736e9310110ec2cb53e56 --- .../tentative/observable-from.any.js | 4855 ++++++++++++++--- 1 file changed, 3955 insertions(+), 900 deletions(-) diff --git a/testing/web-platform/tests/dom/observable/tentative/observable-from.any.js b/testing/web-platform/tests/dom/observable/tentative/observable-from.any.js index ff5eb83516744..4dee29bca2630 100644 --- a/testing/web-platform/tests/dom/observable/tentative/observable-from.any.js +++ b/testing/web-platform/tests/dom/observable/tentative/observable-from.any.js @@ -4898,6 +4898,250 @@ competing " ) ; +/ +/ +This +test +is +like +the +above +ensuring +that +multiple +subscriptions +to +the +same +/ +/ +sync +- +iterable +- +converted +- +Observable +can +exist +at +a +time +. +Since +sync +iterables +/ +/ +push +all +of +their +values +to +the +Observable +synchronously +the +way +to +do +this +/ +/ +is +subscribe +to +the +sync +iterable +Observable +* +inside +* +the +next +handler +of +the +/ +/ +same +Observable +. +test +( +( +) += +> +{ +const +results += +[ +] +; +const +array += +[ +1 +2 +3 +4 +5 +] +; +const +source += +Observable +. +from +( +array +) +; +source +. +subscribe +( +{ +next +: +v += +> +{ +results +. +push +( +v +) +; +if +( +v += += += +3 +) +{ +/ +/ +Pushes +all +5 +values +to +results +right +after +the +first +instance +of +3 +. +source +. +subscribe +( +{ +next +: +v += +> +results +. +push +( +v +) +complete +: +( +) += +> +results +. +push +( +' +inner +complete +' +) +} +) +; +} +} +complete +: +( +) += +> +results +. +push +( +' +outer +complete +' +) +} +) +; +assert_array_equals +( +results +[ +1 +2 +3 +1 +2 +3 +4 +5 +' +inner +complete +' +4 +5 +' +outer +complete +' +] +) +; +} +" +from +( +) +: +Sync +iterable +multiple +in +- +flight +subscriptions +competing +" +) +; promise_test ( async @@ -6169,185 +6413,2709 @@ return ; / / -This -test -exercises +Like the -logic -of -GetIterator -( -) +above +test +but +for async -- -> -sync -fallback -/ -/ -logic +iterables . -Specifically -we -have -an -object -that -is -an +promise_test +( async +t += +> +{ +const +results += +[ +] +; +const iterable -that -is -/ -/ -it -has -a -callback += +{ [ Symbol . asyncIterator ] -implementation -. -Observable +( +) +{ +return +{ +val +: +0 +next +( +) +{ +results . -from +push +( +IteratorRecord +# +next ( ) -/ -/ -detects +pushing +{ this -and -commits -to -converting -the -object +. +val +} +) +; +return +{ +value +: +this +. +val +done +: +this +. +val ++ ++ += += += +10 +? +true +: +false +} +; +} +return +( +reason +) +{ +results +. +push +( +IteratorRecord +# +return +( +) +called +with +reason += +{ +reason +} +) +; +return +{ +done +: +true +} +; +} +} +; +} +} +; +const +ac += +new +AbortController +( +) +; +await +new +Promise +( +resolve += +> +{ +Observable +. from -the -async +( iterable -/ -/ -protocol +) . -Then -after -conversion -but -before -subscription -the -object -is -/ -/ -modified -such -that -it -no -longer -implements -the -async -iterable -protocol +subscribe +( +v += +> +{ +results . -/ -/ -/ -/ -But -since -it -still -implements -the -* -iterable -* -protocol -ECMAScript -' -s -/ -/ -GetIterator +push +( +Observing +{ +v +} +) +; +if +( +v += += += +3 +) +{ +ac +. +abort +( +Aborting +because +v += +{ +v +} +) +; +resolve +( +) +; +} +} +{ +signal +: +ac +. +signal +} +) +; +} +) +; +assert_array_equals +( +results +[ +" +IteratorRecord +# +next +( +) +pushing +0 +" +" +Observing +0 +" +" +IteratorRecord +# +next +( +) +pushing +1 +" +" +Observing +1 +" +" +IteratorRecord +# +next +( +) +pushing +2 +" +" +Observing +2 +" +" +IteratorRecord +# +next +( +) +pushing +3 +" +" +Observing +3 +" +" +IteratorRecord +# +return +( +) +called +with +reason += +Aborting +because +v += +3 +" +] +) +; +} +" +from +( +) +: +Aborting +async +iterable +midway +through +iteration +both +stops +iteration +" ++ +" +and +invokes +IteratorRecord +# +return +( +) +" +) +; +test +( +( +) += +> +{ +const +iterable += +{ +[ +Symbol +. +iterator +] +( +) +{ +return +{ +val +: +0 +next +( +) +{ +return +{ +value +: +this +. +val +done +: +this +. +val ++ ++ += += += +10 +? +true +: +false +} +; +} +/ +/ +Not +returning +an +Object +results +in +a +TypeError +being +thrown +. +return +( +reason +) +{ +} +} +; +} +} +; +let +thrownError += +null +; +const +ac += +new +AbortController +( +) +; +Observable +. +from +( +iterable +) +. +subscribe +( +v += +> +{ +if +( +v += += += +3 +) +{ +try +{ +ac +. +abort +( +Aborting +because +v += +{ +v +} +) +; +} +catch +( +e +) +{ +thrownError += +e +; +} +} +} +{ +signal +: +ac +. +signal +} +) +; +assert_not_equals +( +thrownError +null +" +abort +( +) +threw +an +Error +" +) +; +assert_true +( +thrownError +instanceof +TypeError +) +; +assert_true +( +thrownError +. +message +. +includes +( +' +return +( +) +' +) +) +; +assert_true +( +thrownError +. +message +. +includes +( +' +Object +' +) +) +; +} +" +from +( +) +: +Sync +iterable +: +Iterator +# +return +( +) +must +return +an +Object +or +an +" ++ +" +error +is +thrown +" +) +; +/ +/ +This +test +is +just +like +the +above +but +for +async +iterables +. +It +asserts +that +a +/ +/ +Promise +is +rejected +when +return +( +) +does +not +return +an +Object +. +promise_test +( +async +t += +> +{ +const +iterable += +{ +[ +Symbol +. +asyncIterator +] +( +) +{ +return +{ +val +: +0 +next +( +) +{ +return +{ +value +: +this +. +val +done +: +this +. +val ++ ++ += += += +10 +? +true +: +false +} +; +} +/ +/ +Not +returning +an +Object +results +in +a +rejected +Promise +. +return +( +reason +) +{ +} +} +; +} +} +; +const +unhandled_rejection_promise += +new +Promise +( +( +resolve +reject +) += +> +{ +const +unhandled_rejection_handler += +e += +> +resolve +( +e +. +reason +) +; +self +. +addEventListener +( +" +unhandledrejection +" +unhandled_rejection_handler +) +; +t +. +add_cleanup +( +( +) += +> +self +. +removeEventListener +( +" +unhandledrejection +" +unhandled_rejection_handler +) +) +; +t +. +step_timeout +( +( +) += +> +reject +( +' +Timeout +' +) +3000 +) +; +} +) +; +const +ac += +new +AbortController +( +) +; +await +new +Promise +( +resolve += +> +{ +Observable +. +from +( +iterable +) +. +subscribe +( +v += +> +{ +if +( +v += += += +3 +) +{ +ac +. +abort +( +Aborting +because +v += +{ +v +} +) +; +resolve +( +) +; +} +} +{ +signal +: +ac +. +signal +} +) +; +} +) +; +const +reason += +await +unhandled_rejection_promise +; +assert_true +( +reason +instanceof +TypeError +) +; +assert_true +( +reason +. +message +. +includes +( +' +return +( +) +' +) +) +; +assert_true +( +reason +. +message +. +includes +( +' +Object +' +) +) +; +} +" +from +( +) +: +Async +iterable +: +Iterator +# +return +( +) +must +return +an +Object +or +a +" ++ +" +Promise +rejects +asynchronously +" +) +; +/ +/ +This +test +exercises +the +logic +of +GetIterator +( +) +async +- +> +sync +fallback +/ +/ +logic +. +Specifically +we +have +an +object +that +is +an +async +iterable +that +is +/ +/ +it +has +a +callback +[ +Symbol +. +asyncIterator +] +implementation +. +Observable +. +from +( +) +/ +/ +detects +this +and +commits +to +converting +the +object +from +the +async +iterable +/ +/ +protocol +. +Then +after +conversion +but +before +subscription +the +object +is +/ +/ +modified +such +that +it +no +longer +implements +the +async +iterable +protocol +. +/ +/ +/ +/ +But +since +it +still +implements +the +* +iterable +* +protocol +ECMAScript +' +s +/ +/ +GetIterator +( +) +abstract +algorithm +[ +1 +] +is +fully +exercised +which +is +spec +' +d +to +/ +/ +fall +- +back +to +the +synchronous +iterable +protocol +if +it +exists +and +create +a +/ +/ +fully +async +iterable +out +of +the +synchronous +iterable +. +/ +/ +/ +/ +[ +1 +] +: +https +: +/ +/ +tc39 +. +es +/ +ecma262 +/ +# +sec +- +getiterator +promise_test +( +async +( +) += +> +{ +const +results += +[ +] +; +const +async_iterable += +{ +asyncIteratorGotten +: +false +get +[ +Symbol +. +asyncIterator +] +( +) +{ +results +. +push +( +" +[ +Symbol +. +asyncIterator +] +GETTER +" +) +; +if +( +this +. +asyncIteratorGotten +) +{ +return +null +; +/ +/ +Both +null +and +undefined +work +here +. +} +this +. +asyncIteratorGotten += +true +; +/ +/ +The +only +requirement +for +this +to +be +converted +as +an +async +/ +/ +iterable +- +> +Observable +is +that +the +return +value +be +callable +( +i +. +e +. +a +function +) +. +return +function +( +) +{ +} +; +} +[ +Symbol +. +iterator +] +( +) +{ +results +. +push +( +' +[ +Symbol +. +iterator +] +( +) +invoked +as +fallback +' +) +; +return +{ +val +: +0 +next +( +) +{ +return +{ +value +: +this +. +val +done +: +this +. +val ++ ++ += += += +4 +? +true +: +false +} +; +} +} +; +} +} +; +const +source += +Observable +. +from +( +async_iterable +) +; +assert_array_equals +( +results +[ +" +[ +Symbol +. +asyncIterator +] +GETTER +" +] +) +; +await +new +Promise +( +( +resolve +reject +) += +> +{ +source +. +subscribe +( +{ +next +: +v += +> +{ +results +. +push +( +Observing +{ +v +} +) +; +queueMicrotask +( +( +) += +> +results +. +push +( +next +( +) +microtask +interleaving +( +v += +{ +v +} +) +) +) +; +} +error +: +e += +> +reject +( +e +) +complete +: +( +) += +> +{ +results +. +push +( +' +complete +( +) +' +) +; +resolve +( +) +; +} +} +) +; +} +) +; +assert_array_equals +( +results +[ +/ +/ +Old +: +" +[ +Symbol +. +asyncIterator +] +GETTER +" +/ +/ +New +: +" +[ +Symbol +. +asyncIterator +] +GETTER +" +" +[ +Symbol +. +iterator +] +( +) +invoked +as +fallback +" +" +Observing +0 +" +" +next +( +) +microtask +interleaving +( +v += +0 +) +" +" +Observing +1 +" +" +next +( +) +microtask +interleaving +( +v += +1 +) +" +" +Observing +2 +" +" +next +( +) +microtask +interleaving +( +v += +2 +) +" +" +Observing +3 +" +" +next +( +) +microtask +interleaving +( +v += +3 +) +" +" +complete +( +) +" +] +) +; +} +" +from +( +) +: +Asynchronous +iterable +conversion +with +synchronous +iterable +fallback +" +) +; +test +( +( +) += +> +{ +const +results += +[ +] +; +let +generatorFinalized += +false +; +const +generator += +function +* +( +) +{ +try +{ +for +( +let +n += +0 +; +n +< +10 +; +n ++ ++ +) +{ +yield +n +; +} +} +finally +{ +generatorFinalized += +true +; +} +} +; +const +observable += +Observable +. +from +( +generator +( +) +) +; +const +abortController += +new +AbortController +( +) +; +observable +. +subscribe +( +n += +> +{ +results +. +push +( +n +) +; +if +( +n += += += +3 +) +{ +abortController +. +abort +( +) +; +} +} +{ +signal +: +abortController +. +signal +} +) +; +assert_array_equals +( +results +[ +0 +1 +2 +3 +] +) +; +assert_true +( +generatorFinalized +) +; +} +" +from +( +) +: +Generator +finally +block +runs +when +subscription +is +aborted +" +) +; +test +( +( +) += +> +{ +const +results += +[ +] +; +let +generatorFinalized += +false +; +const +generator += +function +* +( +) +{ +try +{ +for +( +let +n += +0 +; +n +< +10 +; +n ++ ++ +) +{ +yield +n +; +} +} +catch +{ +assert_unreached +( +" +generator +should +not +be +aborted +" +) +; +} +finally +{ +generatorFinalized += +true +; +} +} +; +const +observable += +Observable +. +from +( +generator +( +) +) +; +observable +. +subscribe +( +( +n +) += +> +{ +results +. +push +( +n +) +; +} +) +; +assert_array_equals +( +results +[ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +] +) +; +assert_true +( +generatorFinalized +) +; +} +" +from +( +) +: +Generator +finally +block +run +when +Observable +completes +" +) +; +test +( +( +) += +> +{ +const +results += +[ +] +; +let +generatorFinalized += +false +; +const +generator += +function +* +( +) +{ +try +{ +for +( +let +n += +0 +; +n +< +10 +; +n ++ ++ +) +{ +yield +n +; +} +throw +new +Error +( +' +from +the +generator +' +) +; +} +finally +{ +generatorFinalized += +true +; +} +} +; +const +observable += +Observable +. +from +( +generator +( +) +) +; +observable +. +subscribe +( +{ +next +: +n += +> +results +. +push +( +n +) +error +: +e += +> +results +. +push +( +e +. +message +) +} +) +; +assert_array_equals +( +results +[ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +" +from +the +generator +" +] +) +; +assert_true +( +generatorFinalized +) +; +} +" +from +( +) +: +Generator +finally +block +run +when +Observable +errors +" +) +; +promise_test +( +async +t += +> +{ +const +results += +[ +] +; +let +generatorFinalized += +false +; +async +function +* +asyncGenerator +( +) +{ +try +{ +for +( +let +n += +0 +; +n +< +10 +; +n ++ ++ +) +{ +yield +n +; +} +} +finally +{ +generatorFinalized += +true +; +} +} +const +observable += +Observable +. +from +( +asyncGenerator +( +) +) +; +const +abortController += +new +AbortController +( +) +; +await +new +Promise +( +( +resolve +) += +> +{ +observable +. +subscribe +( +( +n +) += +> +{ +results +. +push +( +n +) +; +if +( +n += += += +3 +) +{ +abortController +. +abort +( +) +; +resolve +( +) +; +} +} +{ +signal +: +abortController +. +signal +} +) +; +} +) +; +assert_array_equals +( +results +[ +0 +1 +2 +3 +] +) +; +assert_true +( +generatorFinalized +) +; +} +" +from +( +) +: +Async +generator +finally +block +run +when +subscription +is +aborted +" +) +; +promise_test +( +async +t += +> +{ +const +results += +[ +] +; +let +generatorFinalized += +false +; +async +function +* +asyncGenerator +( +) +{ +try +{ +for +( +let +n += +0 +; +n +< +10 +; +n ++ ++ +) +{ +yield +n +; +} +} +finally +{ +generatorFinalized += +true +; +} +} +const +observable += +Observable +. +from +( +asyncGenerator +( +) +) +; +await +new +Promise +( +resolve += +> +{ +observable +. +subscribe +( +{ +next +: +n += +> +results +. +push +( +n +) +complete +: +( +) += +> +resolve +( +) +} +) +; +} +) +; +assert_array_equals +( +results +[ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +] +) +; +assert_true +( +generatorFinalized +) +; +} +" +from +( +) +: +Async +generator +finally +block +runs +when +Observable +completes +" +) +; +promise_test +( +async +t += +> +{ +const +results += +[ +] +; +let +generatorFinalized += +false +; +async +function +* +asyncGenerator +( +) +{ +try +{ +for +( +let +n += +0 +; +n +< +10 +; +n ++ ++ +) +{ +if +( +n += += += +4 +) +{ +throw +new +Error +( +' +from +the +async +generator +' +) +; +} +yield +n +; +} +} +finally +{ +generatorFinalized += +true +; +} +} +const +observable += +Observable +. +from +( +asyncGenerator +( +) +) +; +await +new +Promise +( +( +resolve +) += +> +{ +observable +. +subscribe +( +{ +next +: +( +n +) += +> +results +. +push +( +n +) +error +: +( +e +) += +> +{ +results +. +push +( +e +. +message +) +; +resolve +( +) +; +} +} +) +; +} +) +; +assert_array_equals +( +results +[ +0 +1 +2 +3 +" +from +the +async +generator +" +] +) +; +assert_true +( +generatorFinalized +) +; +} +" +from +( +) +: +Async +generator +finally +block +run +when +Observable +errors +" +) +; +/ +/ +Test +what +happens +when +return +( +) +throws +an +error +upon +abort +. +test +( +( +) += +> +{ +const +results += +[ +] +; +const +iterable += +{ +[ +Symbol +. +iterator +] +( +) +{ +return +{ +val +: +0 +next +( +) +{ +results +. +push +( +' +next +( +) +called +' +) +; +return +{ +value +: +this +. +val +done +: +this +. +val ++ ++ += += += +10 +? +true +: +false +} +; +} +return +( +) +{ +results +. +push +( +' +return +( +) +about +to +throw +an +error +' +) +; +throw +new +Error +( +' +return +( +) +error +' +) +; +} +} +; +} +} +; +const +ac += +new +AbortController +( +) +; +const +source += +Observable +. +from +( +iterable +) +; +source +. +subscribe +( +v += +> +{ +if +( +v += += += +3 +) +{ +try +{ +ac +. +abort +( +) +; +} +catch +( +e +) +{ +results +. +push +( +AbortController +# +abort +( +) +threw +an +error +: +{ +e +. +message +} +) +; +} +} +} +{ +signal +: +ac +. +signal +} +) +; +assert_array_equals +( +results +[ +' +next +( +) +called +' +' +next +( +) +called +' +' +next +( +) +called +' +' +next +( +) +called +' +' +return +( +) +about +to +throw +an +error +' +' +AbortController +# +abort +( +) +threw +an +error +: +return ( ) -abstract -algorithm -[ -1 -] -is -fully -exercised -which -is -spec +error ' -d -to -/ -/ -fall -- -back -to -the -synchronous -iterable -protocol -if -it -exists -and -create -a -/ -/ -fully -async -iterable -out -of -the -synchronous -iterable -. -/ -/ -/ -/ -[ -1 ] +) +; +} +" +from +( +) : -https +Sync +iterable : -/ -/ -tc39 -. -es -/ -ecma262 -/ +error +thrown +from +IteratorRecord # -sec -- -getiterator +return +( +) +can +be +" ++ +" +synchronously +caught +" +) +; promise_test ( async -( -) +t = > { @@ -6358,13 +9126,9 @@ results ] ; const -async_iterable +iterable = { -asyncIteratorGotten -: -false -get [ Symbol . @@ -6373,153 +9137,236 @@ asyncIterator ( ) { +return +{ +val +: +0 +next +( +) +{ results . push ( -" -[ -Symbol -. -asyncIterator -] -GETTER -" +' +next +( +) +called +' ) ; -if -( +return +{ +value +: this . -asyncIteratorGotten +val +done +: +this +. +val ++ ++ += += += +10 +? +true +: +false +} +; +} +return +( ) { +results +. +push +( +' return -null +( +) +about +to +throw +an +error +' +) ; / / -Both -null -and -undefined -work -here +For +async +iterables +errors +thrown +in +return +( +) +end +up +in +a +/ +/ +returned +rejected +Promise +so +no +error +appears +on +the +stack +/ +/ +immediately . -} -this +See +[ +1 +] . -asyncIteratorGotten -= -true -; / / -The -only -requirement -for -this -to -be -converted -as -an -async / / -iterable +[ +1 +] +: +https +: +/ +/ +whatpr +. +org +/ +webidl +/ +1397 +. +html +# +async - +iterator +- +close +. +throw +new +Error +( +' +return +( +) +error +' +) +; +} +} +; +} +} +; +const +unhandled_rejection_promise += +new +Promise +( +( +resolve +reject +) += +> +{ +const +unhandled_rejection_handler += +e += > -Observable -is -that -the -return -value -be -callable +resolve ( -i -. e . -a -function +reason ) +; +self . -return -function +addEventListener ( +" +unhandledrejection +" +unhandled_rejection_handler ) -{ -} ; -} -[ -Symbol +t . -iterator -] +add_cleanup ( -) -{ -results -. -push ( -' -[ -Symbol +) += +> +self . -iterator -] +removeEventListener ( +" +unhandledrejection +" +unhandled_rejection_handler ) -invoked -as -fallback -' ) ; -return -{ -val -: -0 -next +t +. +step_timeout +( ( ) -{ -return -{ -value -: -this -. -val -done -: -this -. -val -+ -+ -= = -= -4 -? -true -: -false -} +> +reject +( +' +Timeout +' +) +1500 +) ; } -} +) ; -} -} +const +ac += +new +AbortController +( +) ; const source @@ -6528,22 +9375,7 @@ Observable . from ( -async_iterable -) -; -assert_array_equals -( -results -[ -" -[ -Symbol -. -asyncIterator -] -GETTER -" -] +iterable ) ; await @@ -6561,82 +9393,90 @@ source . subscribe ( -{ -next -: v = > { -results -. -push +if ( -Observing -{ v -} += += += +3 ) -; -queueMicrotask -( +{ +try +{ +ac +. +abort ( ) -= -> +; results . push ( -next -( +' +No +error +thrown +synchronously +' ) -microtask -interleaving +; +resolve ( -v -= -{ -v -} -) -) +' +No +error +thrown +synchronously +' ) ; } -error -: -e -= -> -reject +catch ( e ) -complete -: -( -) -= -> { results . push ( -' -complete +AbortController +# +abort ( ) -' +threw +an +error +: +{ +e +. +message +} ) ; -resolve +reject ( +e ) ; } } +} +{ +signal +: +ac +. +signal +} ) ; } @@ -6646,112 +9486,82 @@ assert_array_equals ( results [ -/ -/ -Old -: -" -[ -Symbol -. -asyncIterator -] -GETTER -" -/ -/ -New -: -" -[ -Symbol -. -asyncIterator -] -GETTER -" -" -[ -Symbol -. -iterator -] -( -) -invoked -as -fallback -" -" -Observing -0 -" -" +' next ( ) -microtask -interleaving -( -v -= -0 -) -" -" -Observing -1 -" -" +called +' +' next ( ) -microtask -interleaving +called +' +' +next ( -v -= -1 ) -" -" -Observing -2 -" -" +called +' +' next ( ) -microtask -interleaving +called +' +' +return ( -v -= -2 ) -" -" -Observing -3 -" -" -next -( +about +to +throw +an +error +' +' +No +error +thrown +synchronously +' +] ) -microtask -interleaving -( -v +; +const +reason = -3 +await +unhandled_rejection_promise +; +assert_true +( +reason +instanceof +Error ) +; +assert_equals +( +reason +. +message " -" -complete +return ( ) +error +" +" +Custom +error +text +passed +through +rejected +Promise " -] ) ; } @@ -6760,13 +9570,25 @@ from ( ) : -Asynchronous -iterable -conversion -with -synchronous +Async iterable -fallback +: +error +thrown +from +IteratorRecord +# +return +( +) +is +" ++ +" +wrapped +in +rejected +Promise " ) ; @@ -6783,109 +9605,137 @@ results [ ] ; -let -generatorFinalized -= -false -; const -generator +iterable = -function -* +{ +impl ( ) { -try +return { -for +next ( -let -n -= -0 -; -n -< -10 -; -n -+ -+ ) { -yield -n +results +. +push +( +' +next +( +) +running +' +) ; -} -} -finally +return { -generatorFinalized -= +done +: true +} ; } } ; -const -observable +} +} +; +iterable +[ +Symbol +. +iterator +] = -Observable +iterable . -from -( -generator -( -) -) +impl ; +{ const -abortController +source = -new -AbortController +Observable +. +from ( +iterable ) ; -observable +source . subscribe ( -n -= -> { -results +} +{ +signal +: +AbortSignal . -push +abort ( -n +) +} ) ; -if +assert_array_equals ( -n -= +results +[ +] +) +; +} +iterable +[ +Symbol +. +iterator +] = +undefined +; +iterable +[ +Symbol +. +asyncIterator +] = -3 -) +iterable +. +impl +; { -abortController +const +source += +Observable . -abort +from ( +iterable ) ; -} +source +. +subscribe +( +{ } { signal : -abortController +AbortSignal . -signal +abort +( +) } ) ; @@ -6893,32 +9743,34 @@ assert_array_equals ( results [ -0 -1 -2 -3 ] ) ; -assert_true -( -generatorFinalized -) -; +} } " from ( ) : -Generator -finally -block -runs -when -subscription -is +Subscribing +to +an +iterable +Observable +with +an aborted +signal +" ++ +" +does +not +call +next +( +) " ) ; @@ -6935,94 +9787,130 @@ results [ ] ; -let -generatorFinalized +const +ac = -false +new +AbortController +( +) ; const -generator +iterable = -function -* +{ +[ +Symbol +. +iterator +] ( ) { -try -{ -for +ac +. +abort ( -let -n -= -0 -; -n -< -10 +) ; -n -+ -+ +return +{ +val +: +0 +next +( ) { -yield -n +results +. +push +( +' +next +( +) +called +' +) ; +return +{ +done +: +true } +; } -catch +return +( +) { -assert_unreached +results +. +push ( -" -generator -should -not -be -aborted -" +' +return +( +) +called +' ) ; } -finally -{ -generatorFinalized -= -true +} ; } } ; const -observable +source = Observable . from ( -generator -( -) +iterable ) ; -observable +source . subscribe ( +{ +next +: +v += +> +results +. +push +( +v +) +complete +: ( -n ) = > -{ results . push ( -n +' +complete +' ) -; +} +{ +signal +: +ac +. +signal } ) ; @@ -7030,37 +9918,30 @@ assert_array_equals ( results [ -0 -1 -2 -3 -4 -5 -6 -7 -8 -9 ] ) ; -assert_true -( -generatorFinalized -) -; } " from ( ) : -Generator -finally -block -run -when -Observable -completes +When +iterable +conversion +aborts +the +subscription +next +( +) +is +" ++ +" +never +called " ) ; @@ -7077,153 +9958,271 @@ results [ ] ; -let -generatorFinalized +const +ac = -false +new +AbortController +( +) ; const -generator +iterable = -function -* +{ +[ +Symbol +. +asyncIterator +] ( ) { -try -{ -for +ac +. +abort ( -let -n -= -0 -; -n -< -10 +) ; -n -+ -+ +return +{ +val +: +0 +next +( ) { -yield -n +results +. +push +( +' +next +( +) +called +' +) +; +return +{ +done +: +true +} ; } -throw -new -Error +return +( +) +{ +results +. +push ( ' -from -the -generator +return +( +) +called ' ) ; } -finally -{ -generatorFinalized -= -true +} ; } } ; const -observable +source = Observable . from ( -generator -( -) +iterable ) ; -observable +source . subscribe ( { next : -n +v = > results . push ( -n +v ) -error +complete : -e +( +) = > results . push ( -e -. -message +' +complete +' ) } +{ +signal +: +ac +. +signal +} ) ; assert_array_equals ( results [ -0 -1 -2 -3 -4 -5 -6 -7 -8 -9 -" -from -the -generator -" ] ) ; -assert_true -( -generatorFinalized -) -; } " from ( ) : -Generator -finally -block -run -when -Observable -errors +When +async +iterable +conversion +aborts +the +subscription +next +( +) +" ++ +" +is +never +called " ) ; +/ +/ +This +test +asserts +some +very +subtle +behavior +with +regard +to +async +iterables +/ +/ +and +a +mid +- +subscription +signal +abort +. +Specifically +it +detects +that +a +signal +/ +/ +abort +ensures +that +the +next +( +) +method +is +not +called +again +on +the +iterator +/ +/ +again +BUT +detects +that +pending +Promise +from +the +* +previous +* +next +( +) +call +/ +/ +still +has +its +IteratorResult +object +examined +. +I +. +e +. +the +implementation +/ +/ +inspecting +the +done +attribute +on +the +resolved +IteratorResult +is +observable +/ +/ +event +after +abort +( +) +takes +place +. promise_test ( async -t +( +) = > { @@ -7234,263 +10233,283 @@ results ] ; let -generatorFinalized +resolveNext = -false +null ; -async -function -* -asyncGenerator +const +iterable += +{ +[ +Symbol +. +asyncIterator +] ( ) { -try +return { -for +next ( -let -n -= -0 -; -n -< -10 -; -n -+ -+ ) { -yield -n +results +. +push +( +' +next +( +) +called +' +) ; -} -} -finally +return +new +Promise +( +resolve += +> { -generatorFinalized +resolveNext = -true +resolve ; } +) +; } -const -observable -= -Observable +return +( +) +{ +results . -from +push ( -asyncGenerator +' +return ( ) +called +' ) ; +} +} +; +} +} +; const -abortController +ac = new AbortController ( ) ; -await -new -Promise -( +const +source += +Observable +. +from ( -resolve +iterable ) -= -> -{ -observable +; +source . subscribe ( -( -n -) +{ +next +: +v = > -{ results . push ( -n +v ) -; -if +complete +: ( -n -= -= -= -3 ) -{ -abortController += +> +results . -abort -( -) -; -resolve +push ( +' +complete +' ) -; -} } { signal : -abortController +ac . signal } ) ; -} -) -; assert_array_equals ( results [ -0 -1 -2 -3 -] -) -; -assert_true +" +next ( -generatorFinalized ) -; -} +called " -from +] +) +; +/ +/ +First +abort +ensuring +return ( ) -: -Async -generator -finally -block -run -when -subscription is -aborted -" +called +. +ac +. +abort +( ) ; -promise_test +assert_array_equals ( -async -t -= -> -{ -const results -= [ -] -; -let -generatorFinalized -= -false -; -async -function -* -asyncGenerator +" +next ( ) -{ -try -{ -for +called +" +" +return ( -let -n -= -0 -; -n -< -10 -; -n -+ -+ ) -{ -yield -n -; -} -} -finally -{ -generatorFinalized -= -true +called +" +] +) ; -} -} -const -observable -= -Observable -. -from -( -asyncGenerator +/ +/ +Then +resolve +the +pending +next ( ) +Promise +to +an +object +whose +done +getter +/ +/ +reports +to +the +test +whether +it +was +accessed +. +We +have +to +wait +one +microtask +/ +/ +for +the +internal +Observable +implementation +to +finish +" +reacting +" +to +said +/ +/ +next +( ) -; +promise +resolution +for +it +to +grab +the +done +attribute +. await new Promise ( -resolve +resolveOuter = > { -observable -. -subscribe +resolveNext ( { -next -: -n -= -> +get +done +( +) +{ results . push ( -n -) -complete -: -( -) -= -> -resolve +' +IteratorResult +. +done +GETTER +' +) +; +resolveOuter ( ) +; +return +true +; +} } ) ; @@ -7501,22 +10520,41 @@ assert_array_equals ( results [ -0 -1 -2 -3 -4 -5 -6 -7 -8 -9 -] +" +next +( ) -; -assert_true +called +" +" +return ( -generatorFinalized +) +called +" +" +IteratorResult +. +done +GETTER +" +/ +/ +Note +that +" +next +( +) +called +" +does +not +make +another +appearance +. +] ) ; } @@ -7525,21 +10563,38 @@ from ( ) : -Async -generator -finally -block -runs -when -Observable -completes +Aborting +an +async +iterable +subscription +stops +subsequent +next +( +) +" ++ +" +calls +but +old +next +( +) +Promise +reactions +are +web +- +observable " ) ; -promise_test +test ( -async -t +( +) = > { @@ -7549,139 +10604,144 @@ results [ ] ; -let -generatorFinalized +const +iterable = -false -; -async -function -* -asyncGenerator +{ +[ +Symbol +. +iterator +] ( ) { -try +return { -for -( -let -n -= +val +: 0 -; -n -< -10 -; -n -+ -+ +next +( ) { -if -( -n +return +{ +value +: +this +. +val +done +: +this +. +val ++ ++ = = = 4 +? +true +: +false +} +; +} +return +( ) { -throw -new -Error +results +. +push ( ' -from -the -async -generator +return +( +) +called ' ) ; } -yield -n -; -} } -finally -{ -generatorFinalized -= -true ; } } +; const -observable +source = Observable . from ( -asyncGenerator -( -) +iterable ) ; -await +const +ac += new -Promise -( +AbortController ( -resolve ) -= -> -{ -observable +; +source . subscribe ( { next : -( -n -) +v = > results . push ( -n +v ) -error +complete : ( -e ) = > -{ results . push ( -e -. -message -) -; -resolve -( +' +complete +' ) -; } +{ +signal +: +ac +. +signal } ) ; -} +ac +. +abort +( ) ; +/ +/ +Must +do +nothing +! assert_array_equals ( results @@ -7690,34 +10750,29 @@ results 1 2 3 -" -from -the -async -generator -" +' +complete +' ] ) ; -assert_true -( -generatorFinalized -) -; } " from ( ) : -Async -generator -finally -block -run -when -Observable -errors +Abort +after +complete +does +NOT +call +IteratorRecord +# +return +( +) " ) ;