Language: Asynchronous Constructors

Created on 16 Jan 2020  路  4Comments  路  Source: dart-lang/language

Users, especially new users, keep running into situations where they would like to create objects where part of the initialziation is asynchronous. They then ask for asynchronous constructors. The answer has, so far, been: Use a static factory function instead.
(https://github.com/dart-lang/sdk/issues/23115, https://stackoverflow.com/questions/38933801/calling-an-async-method-from-component-constructor-in-dart, )

However, there isn't anything inherently impossible about asynchronous constructors.

Generative constructors

Consider the following:

class MyClass<T extends num> {
  final T value1;
  final T value2;
  T _sumCache;
  async MyClass.fromFutures(FutureOr<T> value1, FutureOr<T> value2)
      : value1 = await value1, value2 = await value2 {
    _sumCache = value1 + value2 + await somethingMore;
  }
}

The could define an asynchronous generative constructor. It can be redirecting only if it redirects to another asynchronous generative constructor.

Such a constructor has an implicit return type of Future<MyClass<T>>, and it allows using await in both the initializer list and the constructor body. The async is written up front because it modifies both the implicit return type and the initializer list, not just the body as for normal async functions.

It can only be used as the super-constructor of another asynchronous generative constructor.

As usual, the instance is not available to anyone before the body starts running, and at that point the object state is sound. You can leak the object if you want to, but if not, the caller only gets it when the body completes and the returned future is completed with that instance.

Factory constructors

The same way, we can introduce async factory MyClass(...) for an asynchronous factory constructor. It can either redirect to another async constructor or it can have a body which can then user await.

The real question here is whether it's worth doing since a static factory function works. It has to be backed by a generative constructor, though, which makes it more work to implement than if you could combine everything into just the constructor.
It is a recurring issue for new users, and as such a stumbling block. It's typically solved when the user goes to stackoverflow or our forums, but it's still a negative first/early impression.

request

Most helpful comment

There is also the option of allowing you to write return types on constructors.

If that is allowed, then it might also be possible to write a Future<X> return type which makes it very clear that the constructor does not return an X.

For generative constructors, it would still require the body to be async and implicitly wrap the created object in a future, because there is no return statement that would allow you to return a Future<X> directly. That means that you would probably still need to write async somewhere. Probably before the : of the initializer list, if any, and before the body if there is no initializer list.

None of this is pretty.

I sortof like the idea of writing async early, so you can write

async int foo(int x) => await asyncOp(x);

instead of

Future<int> foo(int x) async => await asyncOp(x);

It would likely reduce the number of accidental

void foo() async { ... }

which should have returned Future<void>.

All 4 comments

As far as I can understand it, the main benefit is the "super" constructor.

It shouldn't be too much of an issue if the class has an init async protected method though:

class Base {
  @protected
  Future<void> init() {...}
}

class Subclass extends Base {
  @override
  Future<void> init() async {
    await super.init();
    // ...
  }
}

As far I as know, no other language has such syntax. So I'm not sure that newcomers would necessarily stop having to go on StackOverflow for such thing.

The async is written up front because it modifies both the implicit return type and the initializer list, not just the body as for normal async functions.

This, to me, is the tricky part of this feature. As you note, unlike using async elsewhere, using it here affects the public API of the constructor, not just its body. I worry that if we give people this, they will place it on constructors just because they want to use await inside the body, and then get very confused when users can no longer call Foo() and synchronously get back an instance of Foo.

Given that potential source of confusion, and the possible implementation complexity of chaining to generative superclass constructors asynchronously, my hunch is that this feature doesn't carry its weight.

There is also the option of allowing you to write return types on constructors.

If that is allowed, then it might also be possible to write a Future<X> return type which makes it very clear that the constructor does not return an X.

For generative constructors, it would still require the body to be async and implicitly wrap the created object in a future, because there is no return statement that would allow you to return a Future<X> directly. That means that you would probably still need to write async somewhere. Probably before the : of the initializer list, if any, and before the body if there is no initializer list.

None of this is pretty.

I sortof like the idea of writing async early, so you can write

async int foo(int x) => await asyncOp(x);

instead of

Future<int> foo(int x) async => await asyncOp(x);

It would likely reduce the number of accidental

void foo() async { ... }

which should have returned Future<void>.

I have this use case where I want to fetch parts of config from my back end because the client wants to be able to change those parameters from their admin panel. The static method workaround is hacky at best. I agree that it would mean a change in the API of the language. But at least factory constructors could be allowed ;)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dev-aentgs picture dev-aentgs  路  3Comments

panthe picture panthe  路  4Comments

moneer-muntazah picture moneer-muntazah  路  3Comments

har79 picture har79  路  5Comments

leonsenft picture leonsenft  路  4Comments