-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Dart2JS has incorrect semantics for yield
in async*
.
#55017
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
No need for importing Flutter, or waiting entire hours. The following has the same behavior. import 'dart:async';
void main() {
initState();
}
void initState() {
_getStream()
.map((event) {
print("Mapped event $event");
return event;
})
.first
.then((value) {
print("Received value $value");
});
}
Stream<String> _getStream() async* {
await Future.delayed(const Duration(seconds: 2));
yield "value";
print("Yielded value");
await Future.delayed(const Duration(seconds: 3));
} It does appear to be a bug. The VM bug for the same thing was #34775 What happens is:
The bug is that A cancel on the subscription of an You can still screw up a correct return by having a Another example of the same thing, exposing what void main() {
var s = _getStream();
var u = s.listen(null);
u.onData((v) async {
print("value: $v");
await u.cancel();
print("cancelled");
});
}
Stream<String> _getStream() async* {
await null;
print("pre-yield");
yield "a";
print("post-yield");
for (var i = 0; i < 5; i++) {
await Future.delayed(Duration(milliseconds: 500));
print("..tick");
}
yield "try again"; // This exits!
print("done");
} The VM runs correctly, printing "cancelled" after "value: a", and nothing more. Compiling to JS prints the "post-yield" and all the ticks before printing "cancelled". I think Dart2Wasm works correctly too, not sure how DDC fares (can never figure out how to run it locally). The existing language test for this issue is (IIRC): language/async_star/async_star_cancel_test |
yield
in async*
.
Thanks for the clarification and detailed explanation. Helps to understand the internals! For my understanding: Is it correct to say, that cancelling in direct (synchronous) response to a yield is an exception to the statement in the documentation (below), because it does not return at the next yield, but immediately after the last/current yield?
|
That, or the statement is being technically correct. But it could be expressed more clearly. If cancelling in synchronous response to an event being delivered, the |
Dart2js transforms async* method bodies into a state machine where each piece of code between an async pause point is a different state. The fix here is to add a check at the beginning of each post-yield state to see if the associated stream is cancelled. If so we can jump to the exit state immediately. I've put together a change implementing this: https://dart-review.googlesource.com/c/sdk/+/355040 |
There is more to it than just exiting if cancelled.
I wrote this about desugaring (I though dart2js used JS generators for That is:
If the state machine can handle the pause before re-entering to the body, that's fine. It likely does need to go back into the body to handle the "return" on a cancel. Whether that's by having a secondary entry point state to use in case of cancel (next If the CL does that, it should be fine. |
… re-entering generator body. If the stream has been closed then re-enter with the appropriate error code. Bug: #55017 Change-Id: Iadf5d039698a48d402a409b5b42ed268885439d1 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/355040 Commit-Queue: Nate Biggs <[email protected]> Reviewed-by: Mayank Patke <[email protected]>
I've landed this fix and previously failing language tests are now passing: https://dart-ci.firebaseapp.com/cl/355040/5 Though it's a bug in the JS runtime, you can workaround this by using a raw Stream/StreamController rather than async*/yield. Given this workaround and the fact that developers are unlikely to encounter this (the bug has existed for a long time), I don't think this warrants a cherrypick. Closing out, thanks for the help here, @lrhn! |
This issue uses a Flutter example, because it was the easiest way to execute code on web for me.
It is not a Flutter issue, though.
Steps to reproduce:
Isn't web behaviour the correct one, because it is consistent with
from https://dart.dev/articles/libraries/creating-streams
Dart Version: 3.19.1
Chrome Version: 122.0.6261.69 arm64
Example
The text was updated successfully, but these errors were encountered: