Skip to content

Prefix await is cumbersome to work with. #25

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

Open
lrhn opened this issue Sep 13, 2018 · 16 comments
Open

Prefix await is cumbersome to work with. #25

lrhn opened this issue Sep 13, 2018 · 16 comments
Assignees
Labels
feature Proposed language feature that solves one or more problems small-feature A small feature which is relatively cheap to implement.

Comments

@lrhn
Copy link
Member

lrhn commented Sep 13, 2018

In an asynchronous Dart function, the await expr expression allows blocking and waiting for a future result of expr to complete.
This is highly convenient compared to using Future.then, but grammatically it's still cumbersome because await is a prefix operator with lower precedence than selectors.
Example:

var x = await (await foo.bar()).baz();

If you need to await an intermediate result of a longer computation chain, you need to add parentheses and go back and write the await far distanced from the operation that created the future.

If you have a cascade like:

expr
  ..bar()
  ..baz()
  ..qux();

and baz is (or becomes) asynchronous and you want to await it before continuing, then you have to rewrite it to something like:

var tmp = expr..bar();
await tmp.baz();
tmp..qux();  // or one dot, doesn't matter.

A solution (proposal!) is be to allow .await as a suffix operator instead of only await as a prefix operator:

var x = foo.bar().await.baz().await;
expr
  ..bar() 
  ..baz().await
  ..qux();

This is still only allowed inside asynchronous functions where await is a reserved word, so it would not be ambiguous.

It's a special syntactic form, not a named member access. An await is a special kind of selector.

You can do expr?.await, expr..await and expr?..await too. It will await (of not null), then throw away the result if it's a cascade (or follow it with more selectors, expr..await.selectors, and still evaluate to the original future ... but why?)

Since this is new syntax, I'd require the operand to have a type which implies a future type (implement Future, be FutureOr, or nullable either of those, or dynamic for being dynamic). No awaiting a non-future. (You can always upcast your int to FutureOr<int> and await that, though. At least you have to be explicit about it.)

See also #1216, #2762, dart-lang/sdk#25986, dart-lang/sdk#23000.

(Edit, 5+ years later: Definitely with a . in front, not foo() await. Grammar is precious, .await is simple, updated to suggest only that.)

@lrhn lrhn added the request Requests to resolve a particular developer problem label Sep 13, 2018
@lrhn lrhn self-assigned this Sep 13, 2018
@pschiffmann
Copy link

pschiffmann commented Jan 26, 2019

Idea: Special-case the await keyword to be a valid RHS for the |> operator from #43. It could look like this:

/// Changes the contents of [file] to uppercase.
/// Completes when the file is written back to disk.
Future<void> contentsToUpperCase(File file) async =>
    file.readAsString()
      |> await
      |. toUpperCase()
      |> file.writeAsString
      |> await;

@fkettelhoit
Copy link

Is this still being considered? It looks like this would be a very small non-breaking syntactic change, but would make working with nested futures much more pleasant. For example, I'm currently working with records that form a graph in a database so that each record has a children getter of type Future<List<Record>> (these graphs can be cyclic, so I do not want to immediately load the children and their descendants). Right now this forces me to write call chains like the following:

final someChild = (await (await (await db.get(someId)).children).first.children).first;

With the proposed postfix await this would become much less cumbersome:

final someChild = db.get(someId).await.children.await.first.children.await.first;

(Of course something even shorter than .await would be great, but now that ! is already used for NNBD I would personally already be very happy with .await or any postfix solution really.)

@mateusfccp
Copy link
Contributor

mateusfccp commented Jun 25, 2020

@fkettelhoit

In these cases I will simply use .then, as function chaining is way clearer than await chaining...

final someChild =
    await db.get(someId)
            .then((o) => o.children)
            .then((o) => o.first.children)
            .then((o) => o.first)

This would be even better if we could have something like #691.

@lrhn lrhn added the small-feature A small feature which is relatively cheap to implement. label Jul 8, 2020
@Jonas-Sander
Copy link

To be honest I think

expr
  ..bar() 
  ..baz() await
  ..qux();

and

expr
  ..bar()
  ..baz().await
  ..qux();

are kinda confusing because I'm just so used to seeing await infront of the Future.
I think the ..baz().await at least shows that the await does indeed wait for baz, in the other one I thought await would wait for qux() at first.

Maybe I'm just boring, but what about this?

expr
  ..bar() 
  ..await baz()
  ..qux();

The only thing i don't like about it is that baz is not directly under bar but I think it's still good enough to quickly scan over.

@lrhn
Copy link
Member Author

lrhn commented Jul 10, 2020

Putting the await before the method call is something I have otherwise thought of, also for other prefix operators.

The await foo().bar() issue is also a problem for !foo.bar(). If you could put prefix operators inside a selector sequence, then you could write that as foo().!bar() (which is perhaps more readable as list.!isEmpty).

It doesn't really work as an incremental change, though. To do this, it would mean that foo.!bar.baz should bind to the bar, not the bar.baz, but then it's inconsistent that !foo.bar.baz does not bind to the foo. We'd have to change the meaning of existing code, making it a breaking language change. For example foo.- is a proposed syntax for doing an operator tear-off. so foo.-[x] would be ambiguous.
So, not sure that feature is so great it's worth a breaking syntax change.

@munificent
Copy link
Member

For reference, Rust decided to use postfix syntax for await. I like prefix await for most cases, but it would be nice to support a postfix form in method chains too.

@boukeversteegh
Copy link

@jsanl wrote:

Maybe I'm just boring, but what about this?

expr
  ..bar() 
  ..await baz()
  ..qux();

Totally in favor of this syntax!

@lrhn
Copy link
Member Author

lrhn commented Sep 9, 2020

Using a prefix operator inline in a selector chain is an enticing idea.
It generalizes, so we could allow foo.!isEmpty or bar.-value as well. The issue with that is that it might impose on our wish to do bar.- as the minus operator tear-off.

@Sufilevy
Copy link

Is this being considered? Are there any developments on the topic?

@insinfo
Copy link

insinfo commented Nov 13, 2023

@lrhn
and I really like the option with dot await .await

@airai-ad-117
Copy link

any updates ?

whats the issue with .await syntax ?

thanks

@Maqcel
Copy link

Maqcel commented Jan 21, 2025

any updates?

needed async in my WebViewController creation and I needed to get rid of the cascade :(

@munificent
Copy link
Member

No updates. We've been focused on larger higher priority issues (metaprogramming, primary constructors, static member shorthands, etc.).

@ghost
Copy link

ghost commented Feb 8, 2025

Maybe this extension method can solve the problem for cascade? (Not sure)

extension <T> on Future<T> {
  Future<T> get wait async => await this;
}

Example:

class A {
  int x = 0;
  Future<int> myMethod() async => 42;
}
void main() async {
  var a=A()
    ..myMethod().wait
    ..x=1;
  print(a.x);
}

My guess is that it doesn't really work as intended, but creates so good an illusion that it can pass for a real thing :-) It also shows that if await suffix gets introduced, it should come with .unawaited variant to distinguish between these cases in cascade.

@munificent
Copy link
Member

My guess is that it doesn't really work as intended

That's correct. The extension method here doesn't actually do anything. The caller would still need to await the result.

@lrhn
Copy link
Member Author

lrhn commented Feb 11, 2025

I would have made unawaited a getter originally, but more people preferred to see the unawaited(...) up front, where the await would have been.
If (when!!) we introduce suffix .await, I'm willing and able to introduce an

extension UnawaitedFuture on Future<Object?> { 
  void get unawaited {} 
}

too. (Or should it be void unawaited() {} so you need to write .unawaited()? Name doesn't work that well for a function, it should be a verb, so future.unawait();. Nailed it!)

@lrhn lrhn removed the request Requests to resolve a particular developer problem label Apr 9, 2025
@lrhn lrhn added the feature Proposed language feature that solves one or more problems label Apr 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems small-feature A small feature which is relatively cheap to implement.
Projects
None yet
Development

No branches or pull requests