Dart Fundamentals – Async / Await

The async and await approach in Dart is very similar to other languages (looking at you C#) which makes it a comfortable topic to grasp for those who have used this pattern in other languages.  However even if you don’t have experience with asynchronous programming using async / await you should find it easy to follow along here.  But please note this article assumes at least a basic familiarity with asynchronous programming.

Async Await in Dart

In a nutshell, you have two keywords to understand – async and await.  Any functions you want to run asynchronously, needs to have the async modifier added to it.  This modifier comes right after the function signature, just like this:

void hello() async {
  print('something exciting is going to happen here...');
}

This is of course a contrived example.  Typically the function you want to run asynchronously would have some expensive operation in it – like file i/o, some sort of computation, or more commonly an API call to a RESTful service.  Don’t worry, we’ll cover more complex scenarios in a bit…

And now we come to await.  The await part basically says – go ahead and run this function asynchronously, and when it is done, continue on to the next line of code.  This is the best part of using async/await – because you can write code that is very easy to follow, like this:

void main() async {
  await hello();
  print('all done');
}

There are two important things to grasp concerning the above block of code. First off, we use the async modifier on the main method because we are going to run the hello() function asynchronously.  And secondarily we place the await modifier directly in front of the function we want to run asynchronously.  Hence why this is frequently referred to as the async/await pattern.

Just remember, if you are going to await, make sure both the caller function and any functions you call within that function all use the async modifier.

How does this work under the hood?

So just how does Dart make this all work?  To really grasp this, you need to understand Dart Futures.  You can skip this section if you want and still use this pattern, but it does help to have the knowledge as to how and why this works.  So here are the finer points of how async and await work in Dart.

  • When you await an async function, execution of the code within the caller suspends while the async operation is executed.
  • When an async operation is completed, the value of what was awaited is contained within the Future

Take a look at the simple program below.  We assign the variable x to the result of the asynchronous function four().  Then we print out its number to prove we got back the expected result.

import 'dart:async';

void main() async {
  var x = await four();
  print(x);
}

Future<int> four() async {
 return 4;
}

A More Realistic Example

So far I have shown you contrived examples that really never needed to be asynchronous.  This was done to keep things as simple as possible.  But now lets do some realistic async / await work here.

Typically, the reason you wanted to do async programming in the first place is because you knew you were going to perform a long running operation and you didn’t want your program to be non-responsive while that operation was running.  Let’s create a long running operation (2 seconds) and do it async.

import 'dart:async';

class Employee {
  int id;
  String firstName;
  String lastName;
  
  Employee(this.id, this.firstName, this.lastName);
}

void main() async {
  print("getting employee...");
  var x = await getEmployee(33);
  print("Got back ${x.firstName} ${x.lastName} with id# ${x.id}");
}

Future<Employee> getEmployee(int id) async {
  //Simluate what a real service call delay may look like by delaying 2 seconds   
  await Future<Employee>.delayed(const Duration(seconds: 2));
  //and then return an employee - lets pretend we grabbed this out of a database 🙂
  var e = new Employee(id, "Joe", "Coder");
  return e;
}

If you run the above code, you see the message “getting employee…” print out immediately.  Then, 2 seconds later, the employee arrives back and the details of that employee are printed out.

Multiple Async Calls

In some languages, having to make multiple async calls can be a real headache if they don’t support the async / await pattern.  This is because you typically would make the first async call and within its callback nest another async call, and so on…  This is referred to as “callback hell”.  With async await, you make the calls linearly and without nesting – just like you would non-async code.  Consider the example below where we have three async methods and we want to call them in order one at a time and do it asynchronously.

import 'dart:async';

Future<String> firstAsync() async {
  await Future<String>.delayed(const Duration(seconds: 2));
  return "First!";
}

Future<String> secondAsync() async {
  await Future<String>.delayed(const Duration(seconds: 2));
  return "Second!";
}

Future<String> thirdAsync() async {
  await Future<String>.delayed(const Duration(seconds: 2));
  return "Third!";
}

void main() async {
  var f = await firstAsync();
  print(f);
  var s = await secondAsync();
  print(s);
  var t = await thirdAsync();
  print(t);
  print('done');
}

If you were to run the above program, you would see the following result shown in your console (note the 2 second delay as well between each call):

First!
Second!
Third!
done

Having fun with Futures

By now you probably realize that to do async programming in Dart, you’ll be working with Futures.  So it would be good to know your way around that class.  Let’s take two scenarios and show how the Future class in Dart handles them – iterating a list of async calls in order, and waiting for all async calls to complete.

Iterating

To iterate a set of async calls, use Future.forEach.  In the example below, we create a method to check if a number is prime or not.  Since we wrap our result coming from isPrimeNumber in a Future, we are able to await the results and invoke then so that we can perform an operation of our choosing against that result (which in our case, we just print out the result).

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

Future<bool> isPrimeNumber(int number) async {  
  if (number == 1) return false;
  if (number == 2) return true;

  double mysqrt = sqrt(number);
  int limit = mysqrt.ceil();

  for (int i = 2; i <= limit; ++i)  {
    if (number % i == 0)  return false;
  }

  return true;
}

void main() async {
  await Future.forEach([1,2,3,4,5,6,7,8,9,10], (int n) => 
    isPrimeNumber(n)
    .then((x) => print("${n}${x ? " is" : " is not"} a prime number")));
  
  print('done!');
}

Waiting

To execute some code when all async calls have completed, use Future.wait. In the example below, we generate a handful of random numbers asynchronously.  When all of the random numbers are generated, we print out the list and find the smallest value.

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

Future<int> getRandomNumber() async {
  var random = new Random();
  return random.nextInt(100);
}

void findSmallestNumberInList(List<int> lst) {
  print("all numbers are in:");
  lst.forEach((l) => print(l));
  lst.sort();
  int largest = lst.first;
  print("The smallest random # we generated was: ${largest}");
}

void main() async {
  Future.wait([getRandomNumber(), getRandomNumber(), getRandomNumber()])
    .then((List<int> results) => findSmallestNumberInList(results));
}

Error Handling

Dart makes handling errors within asynchronous code easy because it utilizes something you are already likely very familiar with – try catch blocks.  If the code within a try block throws an exception, you are going to be able to handle it in the catch block.  In the example below, we intentionally throw an exception in the openFile function just to show you how this works.  If you run this, you would see the “success!” message is never printed and instead we see a message concerning the exception that was thrown.

import 'dart:async';

Future<void> openFile(String fileName) async {
  throw new Exception("boom!");
}

void main() async {
  try
  {
    var result = await openFile("theFile");
    print("success!");
  }
  catch(e) {
    print("Looks like we caught an error: ${e.toString()}");
  }
}

That’s a wrap!

Combined with the Futures class, the async / await pattern in Dart is a powerful and expressive way of doing asynchronous programming.  This is good news because as you get into building web applications or perhaps Flutter, you are going to really need to grasp the fundamentals of asynchronous coding.  Hopefully this post gave you a head start.

1 thought on “Dart Fundamentals – Async / Await”

  1. >> who have used this pattern in other languages
    Do you really think that such way of the asynchronous programming is a pattern, but not a style of programming?
    I don’t think it is a pattern because as states the wikipedia:
    “In software engineering, a software design pattern is a general, reusable solution to a commonly occurring problem within a given context in software design. It is not a finished design that can be transformed directly into source or machine code. It is a description or template for how to solve a problem that can be used in many different situations. Design patterns are formalized best practices that the programmer can use to solve common problems when designing an application or system”.

Leave a Reply

Your email address will not be published. Required fields are marked *