I think there is a bit of misunderstanding about blocking. When you look at your first example - await will block only the rest of your code in your function from executing. The rest of your app will still work just fine.
You need to understand one thing: async/await syntax is just a syntatic sugar for .then(callback) syntax. They both achieve the same thing, only async/await is a lot easier to read, debug and understand. As you can see - in both of your examples you are getting the same result. The question for you is: which syntax do you prefer?
To clarify - let's assume that you want to introduce several wait events of 1 second, and write your a message after each one of these.
Your first example will look like this:
import 'dart:async';
// prints 1000+
void main() async {
Stopwatch watch = Stopwatch();
watch.start();
await Future.delayed(Duration(seconds:1));
print(watch.elapsedMilliseconds);
await Future.delayed(Duration(seconds:1));
print(watch.elapsedMilliseconds);
await Future.delayed(Duration(seconds:1));
print(watch.elapsedMilliseconds);
}
Note how easy is to read the code and understand.
Now, to the second example changed to achieve the same thing:
import 'dart:async';
void main() async {
Stopwatch watch = Stopwatch();
watch.start();
Future.delayed(Duration(seconds:1)).then((_){
print(watch.elapsedMilliseconds);
Future.delayed(Duration(seconds:1)).then((_){
print(watch.elapsedMilliseconds);
Future.delayed(Duration(seconds:1)).then((_){
print(watch.elapsedMilliseconds);
});
});
});
}
They will both achieve the same - but the second example makes your eyes hurt.
One more interesting scenario for you to consider is - what if you want several things happening at the same time? And this is not unusual - if you needed to fetch 3 images from 3 different servers, you would not fetch them sequentially. You would want to fire all 3 requests at the same time, and wait for all of them to finish.
Using the async/await this is very easy:
import 'dart:async';
// prints 1000+
void main() async {
Stopwatch watch = Stopwatch();
watch.start();
var f1 = Future.delayed(Duration(seconds:1));
var f2 = Future.delayed(Duration(seconds:2));
var f3 = Future.delayed(Duration(seconds:3));
await Future.wait([f1, f2, f3]);
print(watch.elapsedMilliseconds);
}
Note that since we don't put await in front of each Future.delayed - which means we will start the delayed future, but we will not wait for it's completion.
You will see that the whole function takes only 3 seconds to complete; since all 3 timers are running at the same time. Future.wait will wait for a list of futures to complete.
Now - it is pretty clear that you don't really need .then() syntax in most of the cases, but I think it will still be applicable in more complex scenarios.
For example: you need to fetch 3 images from 3 servers. Each of those servers has a backup server; if the first server returns null as a result - you need to fetch the resource from the backup server.
Additionaly: if Backup server 1 or Backup server 2 returned null, you need to call server 4 to get a single image.
You could even plot a small graph describing this. Now this is where .then() syntax comes in handy - and we will still combine it with async/await. I think once you fully understand this example - you pretty much understand async/await and .then(). Let's go:
import 'dart:async';
import 'dart:math';
Future<int?> getImage(String server) async {
var rng = Random();
print("Downloading from $server");
// we'll add random delay to simulate network
await Future.delayed(Duration(seconds: rng.nextInt(5)));
print("$server is done");
// high chance of returning null
if (rng.nextInt(10)<7) return null;
return 1;
}
// prints 1000+
void main() async {
Stopwatch watch = Stopwatch();
watch.start();
// get the image from server 1
var f1 = getImage("Server 1").then((data) async {
return data ?? await getImage("Server 1 backup");
});
var f2 = getImage("Server 2").then((data) async {
return data ?? await getImage("Server 2 backup");
});
var f4=Future.wait([f1, f2]).then((data) async {
if (data[0]==null || data[1]==null) {
return [await getImage("Server 4")];
} else {
return data;
}
});
var f3 = getImage("Server 3").then((data) async {
return data ?? await getImage("Server 3 backup");
});
await Future.wait([f3, f4]);
print("elapsed ${watch.elapsedMilliseconds} ms");
}
One new thing here is: .then() will return a future object - which you can still wait with await keyword. Told you it was the same thing....
Without .then() syntax, you would need to create one more async function to handle this, making your code a bit mode complex and more difficult to read. With .then() syntax the code is just a bit more managable. See, again - .then() and async/await are practically the same thing...
Standard async/await helps when things are linear (like in multiple Future.delayed exapmle I showed). But when you get to a complex scenario that can be described via Graph with multiple branches running in parallel - .then() will come in handy.
Edit - Dart being Single Thread
And on Dart being single threaded, think about it this way: your code runs inside Dart engine (or Dart VM), and this code really is single threaded. But any call to the outside world will be run in parallel (calling a remote server, or even calling a local hard-drive, calling other process on the same host like OS - and yes, even calling the Timers like in my example).
Like in my example above: I called 3 remote servers to fetch something, and I chained 3 different callbacks, 1 for each call. And the 'outside world things' - calling the servers - is really happening in parallel. Single threading of Dart simply guarantees that only one line of my code will be executed at any given point of time.
If you came from Java background, you would know how difficult was in Java to synchronize multiple threads: and this is where the code would often break. In Dart, you don't need to worry about this. The real performance optimization is the fact that anything happens outside of Dart VM is really running in parallel - and Dart takes care of it for you.
Now how does this work: event loop. That's a little dart engine that keeps track of all your remote server calls, and when ready calls back your - well, callback procedure. Event loop is the one that takes care that your code processes one request at the time...