Home > database >  How do async/await/then really work in Dart?
How do async/await/then really work in Dart?

Time:12-16

This is maybe a recurring question but I found conflicting answers and I'm now confused as to which of them is the correct one. I thought I understood the concept then I started reading all of those answers and got totally confused so I'm looking for a definite and simple answer to my question that I could easily comprehend.

As per this answer and this article, await is supposed to interrupt code execution and actually wait for the future to complete and then continue executing the rest of the code sequentially. It also suggests that this might block the main thread, which is only logical in that case.

On the other hand, this, this and this video from the flutter team suggest that await is not going to block the rest of code execution and that it's just syntactical sugar to register callbacks to be executed when the future finishes, which is the same thing that then does.

Now, I tried to write a small program to understand which of them is correct and it seems that the first approach is the way to go:

import 'dart:async';

// prints: 
// 1000 
// 1000 
void main() async {
  Stopwatch watch = Stopwatch();
  
  watch.start();
  
  await Future.delayed(Duration(seconds:1)).then((_){print(watch.elapsedMilliseconds);});
  
  print(watch.elapsedMilliseconds); 
  
}

In opposition to:

import 'dart:async';

// prints:
// 0
// 1000 
void main() async {
  Stopwatch watch = Stopwatch();
  
  watch.start();
  
  Future.delayed(Duration(seconds:1)).then((_){print(watch.elapsedMilliseconds);});
  
  print(watch.elapsedMilliseconds);
  
}

So I just want to know why the flutter team and some people are suggesting that await does not block the code execution and how this concept really works.

CodePudding user response:

Actually your both functions are having same Result, let me explain more clearly..

When calling async functions they simply don't block our other part of the application to render. Whatever action we are performing inside will be delayed but the rest part will work as it is.

Now let's come to your examples

import 'dart:async';

// prints 1000 
void main() async {
  Stopwatch watch = Stopwatch();
  
  watch.start();
  
  await Future.delayed(Duration(seconds:1));
  
  print(watch.elapsedMilliseconds); 
  
}

In the above example you are just passing the delay duration and not the callback. So it's treating rest of the part as it's callback which will be called once the duration is completed. Now what are you doing that you are telling your function to wait for a Duration you have provided the execute the further code.

So the result is 1000

In the below example

import 'dart:async';

// prints:
// 0
// 1000 
void main() async {
  Stopwatch watch = Stopwatch();
  
  watch.start();
  
  Future.delayed(Duration(seconds:1)).then((_){print(watch.elapsedMilliseconds);}); // prints 1000 
  
  print(watch.elapsedMilliseconds); // prints 0
  
}

you are properly assigning a callback to Future. So now Future will only hold it's callback the let the rest of the part completed.

This is the reason that it's printing 0 first then after delay of a second it prints 1000 .

And Future and Future delay has different work flow this could be now right way to use await or not.

CodePudding user response:

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")];
  });
  
  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 could fire Server 1 and Server 2; but then you would have to chose: which one do I wait? If you wait for both of them to complete - you might be wasting your time: in case Server 1 returns null, you would be waiting for Server 2 to complete before you could call Server 1 Backup.

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.

  • Related