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.
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.
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.
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
asyncis written up front because it modifies both the implicit return type and the initializer list, not just the body as for normalasyncfunctions.
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 ;)
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 anX.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
returnstatement that would allow you to return aFuture<X>directly. That means that you would probably still need to writeasyncsomewhere. 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
asyncearly, so you can writeinstead of
It would likely reduce the number of accidental
which should have returned
Future<void>.