Future

A future (lower case “f”) is an instance of the Future (capitalized “F”) class. A future represents the result of an asynchronous operation, and can have two states: uncompleted or completed.

When a function returns a Future, it means that it takes a while for its result to be ready, and the result will be available in the future.

The most famous example of a function that may take some time to return the result is http.get(url) which is usually used to call RESTFul API, where we have to send an HTTP request and wait for the response. In the following example, http.get() method returns a Future object:

final response = http.get(url);

Calling a function that returns a Future, will not block your code, that’s why that function is called asynchronous. Instead, it will immediately return a Future object, which is at first uncompleted.

Future<T> means that the result of the asynchronous operation will be of type T. For example, if a function returns Future<String>, this means that in the future, it will provide a string to you. If a future doesn’t produce a usable value, then the future’s type is Future<void>.

A Future object can have two states:

Example of Future that returns nothing

In the following example, fetchUserOrder() returns a future that completes after printing to the console. Because it doesn’t return a usable value, fetchUserOrder() has the type Future<void>.

Future<void> fetchUserOrder() {
  // Imagine that this function is fetching user info from another service or database.
  return Future.delayed(Duration(seconds: 2), () => print('Large Latte'));
}

void main() {
  fetchUserOrder();
  print('Fetching user order...');
}

And the output will be:

Fetching user order...
Large Latte

Accessing Future Values: then, async and await

You can access the result of the Future using either of the following keywords:

Suppose that the getNews() function returns an instance of Future<String>

Future<String> getNews() async {
  final response = await http.get(url);
  return response.body;
}

You can consume the result of this function in either of the following ways:

getNews().then((news) {
  print(news);
});

// or,

String news = await getNews();
print(news);

If you use await keyword before the asynchronous function call, the execution waits for the result of asynchronous operation and then resume the execution. The await keyword can only be used in the body of an async function:

Future<void> foo() async {
  String news = await getNews();
  print(news);
}

The result of an async function must be a Future.

If a function returns a Future, it’s considered asyncrounous; you do not need to mark the body of this function with async keyword. The async keyword is necessary only if you have used the await keyword in the body of your function.

The then method of future object returns a Future<T> as well, it means that you can chain them:

getNews().then(…).then(…).then(…)

The type of Future<T> returned by then method is determined by the return value of the function defined in the body of then.

Completing with an error

In the below example, we throw an exception after 2 seconds of delay.

Future<void> fetchUserOrder() {
// Imagine that this function is fetching user info but encounters a bug
  return Future.delayed(Duration(seconds: 2),
      () => throw Exception('Logout failed: user ID is invalid'));
}

void main() {
  fetchUserOrder();
  print('Fetching user order...');
}

The output will be:

Fetching user order...
Uncaught Error: Exception: Logout failed: user ID is invalid

Handling Error

To handle errors in an async function, use try-catch:

try {
  var order = await fetchUserOrder();
  print('Awaiting user order...');
} catch (err) {
  print('Caught error: $err');
}

Within an async function, you can write try-catch clauses the same way you would in synchronous code. The following example explains this:

Future<void> printOrderMessage() async {
  try {
    var order = await fetchUserOrder();
    print('Awaiting user order...');
    print(order);
  } catch (err) {
    print('Caught error: $err');
  }
}

Future<String> fetchUserOrder() {
  // Imagine that this function is more complex.
  var str = Future.delayed(
      Duration(seconds: 4),
      () => throw 'Cannot locate user order');
  return str;
}

Future<void> main() async {
  await printOrderMessage();
}

The output will be:

Caught error: Cannot locate user order

Future.delayed()

The named constructor delayed of Future class executes a callback after given duration.

final myFuture = Future.delayed(
  const Duration(seconds: 5),
  	(){
  		// Do something here --
	},
);

The first argument is duration that is represented as const Duration(seconds: 5), and second parameter is a callback which will be run after specified duration.