Skip to content

Allow async constructors (or at least factory constructors) #23115

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

Closed
DartBot opened this issue Apr 7, 2015 · 21 comments
Closed

Allow async constructors (or at least factory constructors) #23115

DartBot opened this issue Apr 7, 2015 · 21 comments
Labels
area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). closed-obsolete Closed as the reported issue is no longer relevant type-enhancement A request for a change that isn't a bug

Comments

@DartBot
Copy link

DartBot commented Apr 7, 2015

This issue was originally filed by [email protected]


What steps will clearly show the issue / need for enhancement?

  1. Attempt to create a constructor and mark the body as async

What is the current output?
A warning from the analyzer stating that constructors cannot be marked async

What would you like to see instead?
The ability to mark a constructor async. Or at the very least, allow factory constructors to be marked as async. I ran into a situation where I needed to do some post construction logic using an internal method on the constructed object. The method I wanted to use is an asynchronous method and cannot be changed. I ended up using a "static factory method" along the lines of Class.getInstance(), but it seems like this is a situation that would be ideal for a factory constructor.

What version of the product are you using? On what operating system?
1.9.1 on OSX

Please provide any additional information below.

Example:
class Foo {

  //Not allowed :(
  Foo() async {
    await _bar();
  }

  Foo._internal() {
    //normal construction logic
  }
  
  //Also not allowed :(
  factory Foo.factory() async {
    Foo foo = new Foo._internal();
    await foo._bar();
    return foo;
  }

  //Works! But not ideal :(
  static Future<Foo> getInstance() async {
    Foo foo = new Foo._internal();
    await foo._bar();
    return foo;
  }

  Future _bar() async {
    //do some expensive work asynchronously
  }

}

@lrhn
Copy link
Member

lrhn commented Apr 7, 2015

Asynchronous constructors makes little sense - a constructor must return an instance of the class it's a constructor of, and an async function must return a Future.

What you can do is to have static a function that returns a Future, instead of having a factory constructor.

Naming isn't as easy when you can't rely on a "new" in front of the expression. I have so far used "create" as part of the name when no other name seemed reasonable.
I would also usually do the asynchronous work in the static method before creating the object, instead of having an object exist in an uninitialized state.


Added Area-Language, Triaged labels.

@DartBot
Copy link
Author

DartBot commented Apr 7, 2015

This comment was originally written by [email protected]


@LRN: If you look at the request, you'll see that I mentioned that I ended up using a static method for construction instead of doing the logic in the constructor. While this works, it seemed odd that I had to write a static factory method instead of being able to use a factory constructor. I used the name getInstance, but create would be fine as well.

What really sucks is that I'm now discouraged from using factory constructors in my code for two reasons:

  1. They're not as powerful as the more traditional factory pattern
  2. For the sake of consistency, I don't want to expose two different factory strategies (constructor vs method) to my API's consumer.

I personally don't see any readability or usage issues with something like:

Foo foo = await new Foo();

or even:

new Foo().next((foo){
  //use foo
});

I think this would be a great feature to add to the rich level of async support that Dart already has. If it helps, I'm more than willing to have this thread focus only on factory constructors, since that's what I really wanted to use.

@andrew: Like any project, I would say that what's next should be based on user feedback. I don't wish to get into a full argument about operators and recursion here unless someone thinks it'll add value to discussing this feature. If you'd like, I'd gladly take this discussion to the mailing list, as I have responses to at least some of your questions and concerns.

@DartBot
Copy link
Author

DartBot commented Apr 7, 2015

This comment was originally written by [email protected]


Simple case: I have an object with expensive logic for creating its initial state. It could be file I/O, network requests, data crunching, etc. I don't want the user to use the object until that expensive logic if finished, and I don't want to block while it's being executed.

class Foo {

  var expensiveResult;

  //Also not allowed :(
  factory Foo() async {
    Foo foo = new Foo._internal();
    await foo._expensive();
    return foo;
  }

  Foo._internal() {
    //normal construction logic
  }

  Future _expensive() async {
    //do some expensive work asynchronously
    expensiveResult = ...
  }
}

main() {
  Foo foo = await new Foo(); //foo shouldn't be available until its expensive setup is complete
  expect(foo.expensiveResult, isNotNull);
}

@DartBot
Copy link
Author

DartBot commented Apr 7, 2015

This comment was originally written by @kaendfinger


Why not just assign a completer in the constructor, and ask the user to do something like instance.onReady

class MyClass {
  Future onReady;

  MyClass() {
    onReady = new Future(() {
      // Do Stuff
    });
  }
}

main() async {
  var instance = new MyClass();
  await instance.onReady;
  print("Ready");
}

Imho, you should never do anything expensive in a constructor. Constructing objects should generally be fast. However, for factory constructors, I do see the benefit. What if you load objects from a database? What then do you do with factory constructors?

@DartBot
Copy link
Author

DartBot commented Apr 7, 2015

This comment was originally written by [email protected]


I can definitely see an argument against doing this for non-factory constructors. What really prompted this enhancement request was the inability to use factory constructors for all use cases that traditional factories can cover. I also agree that, in general, you should never have anything expensive inside a constructor, which is why I ended up using a factory method. Right now, factory constructors support complex initialization logic as long as all of that logic is synchronous.

Your approach of exposing a property is a potential solution, but at that point I could just force the user to call the actual async method itself. The goal was to make initializing the default state as consumer-friendly as possible.

@DartBot DartBot added Type-Enhancement area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). labels Apr 7, 2015
@Ticore
Copy link

Ticore commented Jul 4, 2015

I try to implements Future interface, then factory could return a Future and able to be await.

import 'dart:async';
import 'dart:mirrors';

@proxy
class AsyncFact implements Future {
  factory AsyncFact() {
    return new AsyncFact._internal(new Future.delayed(
        const Duration(seconds: 1), () => '[Expensive Instance]'));
  }

  AsyncFact._internal(o) : _mirror = reflect(o);

  final InstanceMirror _mirror;
  @override
  noSuchMethod(Invocation invocation) => _mirror.delegate(invocation);
}

main() async {
  print(await new AsyncFact());
}

@lrhn
Copy link
Member

lrhn commented Jul 6, 2015

@Ticore: I'm not sure what you are asking for here. Your code already returns a future (an AsyncFact even) which you can "await" on.

You can't mark your factory constructor async because even if it worked, it would return a system Future, not an AsyncFact, so it's not a valid return value for a constructor on AsyncFact.

@estill01
Copy link

+1 for async factory constructors. Was my naive approach for constructing a database-backed object for a web-app; didn't work, so everything since then on it has been 'work-around' time for me. Perhaps ideological considerations out-weigh this, but there is justification on the 'batteries included' side of things.

@lrhn
Copy link
Member

lrhn commented Aug 31, 2015

It's not impossible to add to the language, but it will break some of the rules that are generally followed.
One rule is that writing "async" doesn't change the type of the method, it only applies to the body.
If an async constructor factory Foo() async {} returns a Future<Foo>, not a Foo then that's no longer true.

The constructor would have to be a factory constructor anyway. Maybe it should be using async as a prefix:

async factory Foo() => something.something.then((_) => something);

or

async factory Foo() async { await something; return something.else; }

@kevmoo kevmoo added type-enhancement A request for a change that isn't a bug and removed triaged labels Mar 1, 2016
@zidenis
Copy link

zidenis commented May 15, 2016

+1 for async support in factory constructors in order to allow construction of objects with database data and avoid the need to call the async method for complete the object.

@Adam-Vandervorst
Copy link

Is the way in which you create a database dependent objects still with a separate method?

If so I share @zidenis thought on async in factory constructors.

@matanlurey matanlurey added the closed-obsolete Closed as the reported issue is no longer relevant label Jun 25, 2018
@matanlurey
Copy link
Contributor

matanlurey commented Jun 25, 2018

Now that new is optional, it is almost identical to have a factory constructor static method:

class A {
  factory A.a() => ...
}

class B {
  static B b() => ...
}

void main() {
  A.a();
  B.b();
}

... and of course, static methods can have a return type of Future:

import 'dart:async';

class C {
  static Future<C> load(String url) => ... 
}

@lrhn Unless you see this happening (re-open in that case), let's close this.

rajadain added a commit to rajadain/mmw_flutter that referenced this issue Aug 20, 2018
This class can be initialized with a username / password
combo, which it uses to get a token and stores that token.
This token will be used for all other methods / requests
it provides.

Dart does not support async factory constructors (see dart-lang/sdk#23115)
so we use a static method instead. What is interesting is
that since all of this depends on a Network request, all
the steps in the pipeline return a Future. However, at
some point, we're going to need an actual instance before
we can proceed. Will be interesting to see how we get there.
@zimmi
Copy link

zimmi commented Oct 13, 2018

@matanlurey That's true, with a small caveat: the default constructor name (the best name, sadly) can't be used for static methods. This doesn't work:

import 'dart:async';

class C {
  static Future<C>(String url) => ... 
}

var c = await C('http://google.com');

@pixeliner
Copy link

Is it really going to end here?

I'm amazed that not a lot of people are encouraging this :/

I've come across quite a few moments now where it really could have made everything smoother, like a Settings class with data from your "dynamic" json file for example.

@konsultaner
Copy link

@matanlurey why is this closed?? I started learning dart 2 days ago and I already need async factory constructors. This came super natural. why is this so unwanted?

@gisthere
Copy link

The same. I use flutter sdk and I need to call async code within constructor:
await _channel.invokeMethod('mymethod')
but I can't.
Solutions with static methods looks ugly.

@konsultaner
Copy link

@rajadain could you reopen?

@rajadain
Copy link

@konsultaner sorry mate, not up to me, but dart-lang/language#782 looks promising.

@dovecheng
Copy link

dovecheng commented Sep 18, 2020

I wrote this test code using dart2.7 and flutter 1.20 version, which can be implemented in two ways.

support flutter project.

import 'dart:async';
import 'dart:math';

import 'package:meta/meta.dart';

mixin AsyncInitMixin<T extends Future> implements Future {
  bool _isReady;

  Future<T> _onReady;

  bool get isReady => _isReady ?? false;

  Future<T> get onReady => _onReady ??= _init();

  Future<T> _init() async {
    await init();
    _isReady = true;
    return this as T;
  }

  @override
  Stream<T> asStream() => onReady.asStream();

  @override
  Future<T> catchError(Function onError, {bool Function(Object error) test}) => onReady.catchError(onError, test: test);

  @override
  Future<R> then<R>(FutureOr<R> Function(T value) onValue, {Function onError}) =>
      onReady.then(onValue, onError: onError);

  @override
  Future<T> timeout(Duration timeLimit, {FutureOr Function() onTimeout}) =>
      onReady.timeout(timeLimit, onTimeout: onTimeout);

  @override
  Future<T> whenComplete(FutureOr<void> Function() action) => onReady.whenComplete(action);

  @protected
  Future<void> init();
}

class Test with AsyncInitMixin {
  String data;

  Test({this.data});

  @override
  Future<void> init() async {
    print('init: A ${DateTime.now()}, now data is $data');
    await Future.delayed(Duration(seconds: 1));
    data = '${Random().nextInt(99999999)}';
    print('init: B ${DateTime.now()}, new data is $data');
  }
}

main() async {
  print('test1 await with constructor');
  final Test test1 = await Test(data: 'aaa'); // await constructor
  await test1;
  print('test1 result is ${test1}, and data is ${test1.data}');

  print('--=--=--=--=--=--');

  print('test2 await with onReady');
  final Test test2 = Test(data: 'bbb');
  await test2;
  await test2.onReady;
  print('test2 result is ${test2}, and data is ${test2.data}');

  print('=-=-=-=-=-=-=-=-=');

  print('test1 await with onReady, ensure no reinit');
  await test1.onReady;
  await test1.onReady;
  await test1.onReady;
  print('test1 result is ${test1}, and data is ${test1.data}');
}

The following is the console output log

test1 await with constructor
init: A 2020-09-18 17:45:12.139703, now data is aaa
init: B 2020-09-18 17:45:13.157902, new data is 13464973
test1 result is Instance of 'Test', and data is 13464973
--=--=--=--=--=--
test2 await with onReady
init: A 2020-09-18 17:45:13.158742, now data is bbb
init: B 2020-09-18 17:45:14.168235, new data is 6139666
test2 result is Instance of 'Test', and data is 6139666
=-=-=-=-=-=-=-=-=
test1 await with onReady, ensure no reinit
test1 result is Instance of 'Test', and data is 13464973

@seyitgkc
Copy link

seyitgkc commented Jun 3, 2021

I wrote this test code using dart2.7 and flutter 1.20 version, which can be implemented in two ways.

support flutter project.

import 'dart:async';
import 'dart:math';

import 'package:meta/meta.dart';

mixin AsyncInitMixin<T extends Future> implements Future {
  bool _isReady;

  Future<T> _onReady;

  bool get isReady => _isReady ?? false;

  Future<T> get onReady => _onReady ??= _init();

  Future<T> _init() async {
    await init();
    _isReady = true;
    return this as T;
  }

  @override
  Stream<T> asStream() => onReady.asStream();

  @override
  Future<T> catchError(Function onError, {bool Function(Object error) test}) => onReady.catchError(onError, test: test);

  @override
  Future<R> then<R>(FutureOr<R> Function(T value) onValue, {Function onError}) =>
      onReady.then(onValue, onError: onError);

  @override
  Future<T> timeout(Duration timeLimit, {FutureOr Function() onTimeout}) =>
      onReady.timeout(timeLimit, onTimeout: onTimeout);

  @override
  Future<T> whenComplete(FutureOr<void> Function() action) => onReady.whenComplete(action);

  @protected
  Future<void> init();
}

class Test with AsyncInitMixin {
  String data;

  Test({this.data});

  @override
  Future<void> init() async {
    print('init: A ${DateTime.now()}, now data is $data');
    await Future.delayed(Duration(seconds: 1));
    data = '${Random().nextInt(99999999)}';
    print('init: B ${DateTime.now()}, new data is $data');
  }
}

main() async {
  print('test1 await with constructor');
  final Test test1 = await Test(data: 'aaa'); // await constructor
  await test1;
  print('test1 result is ${test1}, and data is ${test1.data}');

  print('--=--=--=--=--=--');

  print('test2 await with onReady');
  final Test test2 = Test(data: 'bbb');
  await test2;
  await test2.onReady;
  print('test2 result is ${test2}, and data is ${test2.data}');

  print('=-=-=-=-=-=-=-=-=');

  print('test1 await with onReady, ensure no reinit');
  await test1.onReady;
  await test1.onReady;
  await test1.onReady;
  print('test1 result is ${test1}, and data is ${test1.data}');
}

The following is the console output log

test1 await with constructor
init: A 2020-09-18 17:45:12.139703, now data is aaa
init: B 2020-09-18 17:45:13.157902, new data is 13464973
test1 result is Instance of 'Test', and data is 13464973
--=--=--=--=--=--
test2 await with onReady
init: A 2020-09-18 17:45:13.158742, now data is bbb
init: B 2020-09-18 17:45:14.168235, new data is 6139666
test2 result is Instance of 'Test', and data is 6139666
=-=-=-=-=-=-=-=-=
test1 await with onReady, ensure no reinit
test1 result is Instance of 'Test', and data is 13464973

Hi, thanks for the structure! I have a question. I have to use this with a named constructor. How I can do it? Thanks in advance.

@cedvdb
Copy link
Contributor

cedvdb commented Feb 10, 2022

@matanlurey That's true, with a small caveat: the default constructor name (the best name, sadly) can't be used for static methods. This doesn't work:

import 'dart:async';

class C {
  static Future<C>(String url) => ... 
}

var c = await C('http://google.com');

I disagree with this. Having work, especially async in constructors is bad practice because it can be hard to unit test. Since there is an await in front I don't know to which extent that argument holds but for the sake of consistency I much prefer having a name that symbolize async work. eg in your case C.withNetworkData('google.com'). Breaking that expectation on how code is supposed to behave tends to lead to all sort of shenanigans.

factory instead of static method on the other end provides intent which is generally good for code clarity. So personally, I'm for async named factories but against async constructors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). closed-obsolete Closed as the reported issue is no longer relevant type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests