Let's assume that an initialization of MyComponent in Dart requires sending an HttpRequest to the server. Is it possible to construct an object synchronously and defer a 'real' initialization till the response come back?
In the example below, the _init() function is not called until "done" is printed. Is it possible to fix this?
import 'dart:async';
import 'dart:io';
class MyComponent{
MyComponent() {
_init();
}
Future _init() async {
print("init");
}
}
void main() {
var c = new MyComponent();
sleep(const Duration(seconds: 1));
print("done");
}
Output:
done
init
Probably the best way to handle this is with a factory function, which calls a private constructor.
In Dart, private methods start with an underscore, and "additional" constructors require a name in the form ClassName.constructorName, since Dart doesn't support function overloading. This means that private constructors require a name, which starts with an underscore (MyComponent._create in the below example).
import 'dart:async';
import 'dart:io';
class MyComponent{
/// Private constructor
MyComponent._create() {
print("_create() (private constructor)");
// Do most of your initialization here, that's what a constructor is for
//...
}
/// Public factory
static Future<MyComponent> create() async {
print("create() (public factory)");
// Call the private constructor
var component = MyComponent._create();
// Do initialization that requires async
//await component._complexAsyncInit();
// Return the fully initialized object
return component;
}
}
void main() async {
var c = await MyComponent.create();
print("done");
}
This way, it's impossible to accidentally create an improperly initialized object out of the class. The only available constructor is private, so the only way to create an object is with the factory, which performs proper initialization.
A constructor can only return an instance of the class it is a constructor of (MyComponent). Your requirement would require a constructor to return Future<MyComponent> which is not supported.
You either need to make an explicit initialization method that needs to be called by the user of your class like:
class MyComponent{
MyComponent();
Future init() async {
print("init");
}
}
void main() async {
var c = new MyComponent();
await c.init();
print("done");
}
or you start initialization in the consturctor and allow the user of the component to wait for initialization to be done.
class MyComponent{
Future _doneFuture;
MyComponent() {
_doneFuture = _init();
}
Future _init() async {
print("init");
}
Future get initializationDone => _doneFuture
}
void main() async {
var c = new MyComponent();
await c.initializationDone;
print("done");
}
When _doneFuture was already completed await c.initializationDone returns immediately otherwise it waits for the future to complete first.
I agree, an asynchronous factory function would help Dart devs with this problem. #kankaristo has IMHO given the best answer, a static async method that returns a fully constructed and initialized object. You have to deal with the async somehow, and breaking the init in two will lead to bugs.
Related
How can I use hashIt function in setter if editor gives this error
The modifier async can not by applied to the body of a setter
Future<String> hashIt(String password) async {
return await PasswordHash.hashStorage(password);
}
set hashPass(String pass) async { // error here
final hash = await hashIt(pass);
_hash = hash;
}
compiller message: Error: Setters can't use 'async', 'async*', or 'sync*'.
The reason a setter cannot be async is that an async function returns a future, and a setter does not return anything. That makes it highly dangerous to make a setter async because any error in the setter will become an uncaught asynchronous error (which may crash your program).
Also, being async probably means that the operation will take some time, but there is no way for the caller to wait for the operation to complete. That introduces a risk of race conditions.
So, it's for your own protections.
If you need to do something asynchronous inside the setter anyway, perhaps log something after doing the actual setting, you have a few options.
The simplest is to just call an async helper function:
set foo(Foo foo) {
_foo = foo;
_logSettingFoo(foo);
}
static void _logSettingFoo(Foo foo) async {
try {
var logger = await _getLogger();
await logger.log("set foo", foo);
logger.release(); // or whatever.
} catch (e) {
// report e somehow.
}
}
This makes it very clear that you are calling an async function where nobody's waiting for it to complete.
If you don't want to have a separate helper function, you can inline it:
set foo(Foo foo) {
_foo = foo;
void _logSettingFoo() async {
...
}
_logSettingFoo();
}
or even
set foo(Foo foo) {
_foo = foo;
() async {
...foo...
}();
}
What pattern should I use in this example to load and process some data. As value returns a value, it's not acceptable to have d as a Future. How can I get the constructor to wait until load has completed before continuing?
void main() {
var data = new Data(); // load data
print(data.value()); // data.d is still null
}
class Data {
String d;
Data() {
load();
}
Future<void> load() async {
d = await fn(); // some expensive function (e.g. loading a database)
}
String value() {
return d;
}
}
You cannot make a constructor asynchronous.
An asynchronous function needs to return a Future, and a constructor needs to return an instance of the class itself. Unless the class is a future, the constructor cannot be asynchronous (and even then, it's not really the same thing, and you can't use async/await).
So, if your class needs asynchronous set-up, you should provide the user with a static factory method instead of a constructor. I'd usually hide the constructor then.
class Data {
String _d;
Data._();
static Future<Data> create() async {
var data = Data._();
await data._load();
return data;
}
Future<void> _load() async {
_d = await fn();
}
String get value => _d;
}
As an alternative design, I wouldn't even put the load method on the class, just do the operation in the static factory method:
class Data {
String _d;
Data._(this._d);
static Future<Data> create() async => Data._(await fn());
String get value => _d;
}
Obviously other constraints might require that load has access to the object.
callbacks or asynchronous methods or other options
A solution to the callback plague is "await" and "async" or more specifacally 'dart:async' library.
Now, what is the cost of asynchrony?
When should we not use them?
What are the other alternatives?
The below is a badly coded non-polymer custom element that acts like a messageBox in desktop environment. It gives me less braces and parenthesis-es but requires the caller to be also async or use "show().then((v){print(v);});" pattern. Should I avoid the pattern like this?
Is callback better? Or there is an even smarter way?
Polling version
import 'dart:html';
import 'dart:async';
void init(){
document.registerElement('list-modal',ListModal);
}
class ListModal extends HtmlElement{
ListModal.created():super.created();
String _modal_returns="";
void set modal_returns(String v){
///use the modal_returns setter to
///implement a custom behaviour for
///the return value of the show method
///within the callback you can pass on calling append .
_modal_returns=v;
}
factory ListModal(){
var e = new Element.tag('list-modal');
e.style..backgroundColor="olive"
..position="absolute"
..margin="auto"
..top="50%"
..verticalAlign="middle";
var close_b = new DivElement();
close_b.text = "X";
close_b.style..right="0"
..top="0"
..margin="0"
..verticalAlign="none"
..backgroundColor="blue"
..position="absolute";
close_b.onClick.listen((_){
e.hide();
});
e.append(close_b,(_)=>e.hide());
e.hide();
return e;
}
#override
ListModal append(
HtmlElement e,
[Function clickHandler=null]
){
super.append(e);
if(clickHandler!=null) {
e.onClick.listen(clickHandler);
}else{
e.onClick.listen((_){
this.hide();
_modal_returns = e.text;
});
}
return this;
}
Future<String> show() async{
_modal_returns = '';
this.hidden=false;
await wait_for_input();
print(_modal_returns);
return _modal_returns;
}
wait_for_input() async{
while(_modal_returns=="" && !this.hidden){
await delay();
}
}
void hide(){
this.hidden=true;
}
Future delay() async{
return new Future.delayed(
new Duration(milliseconds: 100));
}
}
Non-polling version
In response to Günter Zöchbauer's wisdom(avoid polling), posting a version that uses a completer. Thanks you as always Günter Zöchbauer:
import 'dart:html';
import 'dart:async';
void init(){
document.registerElement('list-modal',ListModal);
}
class ListModal extends HtmlElement{
ListModal.created():super.created();
String _modal_returns="";
Completer _completer;
void set modal_returns(String v){
///use the modal_returns setter to
///implement a custom behaviour for
///the return value of the show method.
///Use this setter within the callback for
///append. Always call hide() after
///setting modal_returns.
_modal_returns=v;
}
factory ListModal(){
var e = new Element.tag('list-modal');
e.style..backgroundColor="olive"
..position="absolute"
..margin="auto"
..top="50%"
..verticalAlign="middle";
var close_b = new DivElement();
close_b.text = "X";
close_b.style..right="0"
..top="0"
..margin="0"
..verticalAlign="none"
..backgroundColor="blue"
..position="absolute";
close_b.onClick.listen((_){
e.hide();
});
e.append(close_b,(_){e.hide();});
e.hide();
return e;
}
#override
ListModal append(
HtmlElement e,
[Function clickHandler=null]
){
super.append(e);
if(clickHandler!=null) {
e.onClick.listen(clickHandler);
}else{
e.onClick.listen((_){
_modal_returns = e.text;
this.hide();
});
}
return this;
}
Future<String> show() async{
_modal_returns = '';
_completer = new Completer();
this.hidden=false;
return _completer.future;
}
void hide(){
hidden=true;
_completer?.complete(_modal_returns);
_completer=null;
}
}
Usually there is no question whether async should be used or not. Usually one would try to avoid it. As soon as you call an async API your code goes async without a possibility to choose if you want that or not.
There are situations where async execution is intentionally made async. For example to split up large computation in smaller chunks to not starve the event queue from being processed.
On the server side there are several API functions that allow to choose between sync and async versions. There was an extensive discussion about when to use which. I'll look it up and add the link.
The disadvantages of using async / await instead of .then() should be minimal.
minimal Dart SDK version with async / await support is 1.9.1
the VM needs to do some additional rewriting before the code is executed the first time, but this is usually neglectable.
Your code seems to do polling.
wait_for_input() async {
while(_modal_returns=="" && !this.hidden){
await delay();
}
}
This should be avoided if possible.
It would be better to let the modal manage its hidden state itself (by adding a hide() method for example), then it doesn't have to poll whether it was hidden from the outside.
I'm trying this in Dart:
import 'dart:convert';
import 'dart:html';
class testHandler {
Map parsedJSON;
testHandler();
void Initialize(){
String rawJSON = "core/testConfiguration.json";
HttpRequest.getString(rawJSON)
.then((String f) => parsedJSON.from(JSON.decode(f)))
.catchError((Error e) => print(e.toString()));
print(parsedJSON);
}
}
If you see I'm setting parsedJSON in .then() but when I'm trying to get the var, it returns null.
print(parsedJSON); is executed before getString() returns. getString() is async and the callback passed to then() will be executed sometimes later after getString() returned the result but print(parsedJSON); will be executed immediately.
Using async/await makes this quite easy:
import 'dart:convert';
import 'dart:html';
class testHandler {
Map parsedJSON;
testHandler();
Future Initialize() async {
String rawJSON = "core/testConfiguration.json";
try {
String f = await HttpRequest.getString(rawJSON);
parsedJSON = JSON.decode(f);
} catch(Error e) {
print(e.toString());
}
print(parsedJSON);
}
}
Async is contagious therefore code calling Initialize() has to wait for it to finish as well.
No, you are not setting parsedJSON in .then(). You are trying to call method from null object. Before use parsedJSON you should set it with = operator, like
parsedJSON = new Map.from(JSON.decode(f));
In other words, you mixed up parsedJSON's methods and Map's constructors.
P.S.
And, as Gunter denoted it, you may write it shortly:
parsedJSON = JSON.decode(f);
trying to block the execution within a setter until the filed value changes and i know that it will change within a few microseconds, to demonstrate the problem i wrote:
import 'dart:async';
void main() {
new Timer.periodic(new Duration(seconds:1),(t)=>print(Store.x));
new Timer.periodic(new Duration(seconds:3),(t)=>Store.x='initialized');
}
class Store{
static String _x = null;
static set x(v) => _x=v;
static get x{
//how do i block here until x is initialized
return _x;
}
}
A while(x==null); caused stackoverflow, any idea how to do this properly within the setter?
basically i want the setter to return the value when its initialized it should never return null.
This can't be done.
Dart is single-threaded. If you stop execution the code updating the field can't be executed.
If you want something like that you need to switch to async execution.
import 'dart:async';
void main() {
new Timer.periodic(new Duration(seconds:1),(t)=>print(Store.x));
new Timer.periodic(new Duration(seconds:3),(t)=>Store.x='initalized');
}
class Store{
static String _x = null;
static set x(v) => _x=v;
static Future<String> get x async {
while(x == null) {
await new Future.delayed(const Duration(milliseconds: 20),
}
return _x;
}
}
func someFunc() async {
var x = await new Store.x;
}
I wouldn't consider this Future.delayed() good design for this use case. It should be implemented in a way that Store.x fires an event or completes a future when the value changed.