-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
"await" doesn't wait for nested future in Future<Future<void>> #52621
Comments
Also slightly odd to me.. Here's a variation of the code above that has the same behaviour. But if you change the return type of import 'dart:async';
import 'package:test/test.dart';
Future<void> main() async {
await run();
print('FINISHED');
}
// Changing the return type here to be FutureOr instead of Future also fixes the issue.
Future<void> run() => runImpl(doWork);
FutureOr<T> runImpl<T>(
FutureOr<T> Function() operation,
) async {
return await operation();
}
Future<void> doWork() async {
await pumpEventQueue(times: 5000);
print('DOING WORK');
} |
(I'll admit I'm not sure precisely what behavior is intended/desired for the examples here, and which actual happenings are good/bad. So, generally...)
That is not the case, and hasn't been since Dart 1. It's just not compatible (or at least manageable) with the stricter type-safety. In fact, it's possible to have an await with a value that is just a single future, where that future is not awaited. Because awaiting it could be unsound, and being unsound is not no longer something we can accept. Example: void main() async {
Object o = Future<Object?>.value(42);
var o2 = await o;
print(o.runtimeType); // Future<Object?>.
} This future is not awaited because it could be unsound to do so. The static type of So it treats And we stopped recursively awaiting nested futures in Dart 2.0, because the logic needed to find out what the type of So: Now, the issues I see here are 1) that your original code infers the final Future<void> Function() work;
Future<void> run() => run2(work);
FutureOr<T> run2<T>(
FutureOr<T> Function() operation,
) async {
print("run2: $T"); // Future<void>
return await operation();
} That is slightly weird, and bad inference. That inference, and nested If I change var runner = WorkRunner(() async {
await Future.delayed(Duration(seconds:1));
print('DOING WORK');
});
var p = runner.run();
print([p].runtimeType); // List<Future<void>>
print(p.runtimeType); // Future<Future<void>>
await p; we see that the static type of (That's on the VM, dart2js throws a type error about some Pragmatical solution: And possibly issue 2) that |
All the above to say: This is "working as intended", for some definition of "intended" which leans heavily towards "how it happens to be" when it comes to tricky type inference cases. We do not recursively await futures of futures. So, if anything, this can be turned into a request for better inference, but it's not a bug in the implementation of futures or |
Not closed: #50601. |
Thanks for the info! This seems to add up (I found thar removing
I couldn't find this, but I would 👍 it if it would avoid this :-)
Oh, maybe this is part of the reason I thought this happened then. Although I also tried some other combinations I thought would be "nested Futures" and those worked: print(await (Completer()..complete((Completer()..complete(1)).future)).future);
print(await Future.value(Future.value(1))); Is there something special about
Ofc, I wasn't doing this deliberately - the real code was more complex and had more indirection.. There were lots of I guess my next question - what can I (or others) do to accidentally avoid doing this in future? Are there existing lints I could enable that would have prevented me doing this? (for example if "Never use FutureOr in x contexts" is the fix, should there be a lint to avoid you doing this accidentally?). You mentioned |
The var c1 = Completer<Object>();
var c2 = Future<Object?>.value(null);
c1.complete(c2);
c1.future.then((v) {
print(v.runtimeType); // Future<Object?>
}); Same for the I don't know if there are existing ways to avoid this problem. Maybe we should just have a lint warning if here is any |
I'll close this as "working as intended" for now. If we want to ask for better type inference, we should make a more focused request for that. |
I can file an issue if that makes sense - although honestly I'm not sure exactly what I'm asking for :) Given some of the examples above, if the type inference was "improved", what would the new behaviour be? I came up with some other code that feels like it should have an error: import 'dart:async';
abstract class A {
FutureOr<T> work<T>();
Future<void> run() => work(); // Why is this valid?
} Although I'm not sure it's the same as above, because I don't think it's possible to actually implement |
import 'dart:async';
FutureOr<T> work<T>() {
print(T);
return Future<T>.error(0);
}
Future<void> run() => work();
void main() => run().ignore(); This is accepted with no diagnostic messages, runs, and prints 'Future<void>' (in the current dart-pad). Hence, the inferred type argument for the invocation So we're returning an expression of type So the only part which could be wrong is that If it is inferred as I don't see anything wrong in this. [Edit: There was no reason to change |
I guess it seemed to me like we had a method that was returning a This isn't allowed for non-void types, and to me it felt like these were equivalent: typedef FutureInt = Future<int>;
FutureOr<FutureInt> _getFutureOrInt() => Future<int>.value(1);
FutureInt i = _getFutureOrInt(); // 'FutureOr<Future<int>>' can't be assigned to 'Future<int>' But, since that's apparently not the case I guess my question is now - if this can be improved (it doesn't seem ideal to accidentally not await something you think is being awaited, however it also sounds like my understanding that an If it's deemed nothing should change here, and the real issue is just that my assumption that I'd never get another |
True, but when this is a
That's because it isn't a subtype (and it isn't
We wouldn't have that option here, because there are no async function bodies anywhere, but the underlying problems is probably that it is confusing and error prone whenever it is "forgotten" (by an upcast) that any particular expression may or will yield a future. I think it would make sense to adjust the lint 'discarded_futures' to detect that kind of situation more aggressively (or create a new lint doing that), because that's essentially what we have: |
Concerning the original example, my first idea would be that a function whose body is We can always use I guess this could be a lint. In particular, we should have |
Thanks - changing to |
Thanks! I added a comment there, to the effect that it could be useful for the lint to target a slightly larger set of situations. |
My understanding is that if I
await
a function that returns aFuture
, if thatFuture
completes with aFuture
, that would be awaited to. And that seems to happen for most examples. However, when working on some code in the analysis server today I found that wasn't happening. I think it might be related toFutureOr
but any further attempt to simplify my example seem to "fix" the problem.The smallest repro I have is this:
As far as I can tell, all futures are awaited. If I
print(runner.run())
I seeFuture<Future<void>>
. The output when run is:Removing
async
fromrun2
fixes the issue (as do all sorts of other small modifications). I suspect it's working as intended, but I don't understand why (and since there are no warnings, it feels easy to get wrong - I just happened to catch this with a test).The text was updated successfully, but these errors were encountered: