flutter
/

Dart Future – Asynchronous Programming with Futures

Last Sync: Today

On this page

9
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

Dart Future – Asynchronous Programming with Futures

What is a Future?

A Future represents a potential value or error that will be available at some time in the future. It's a core part of Dart's asynchronous programming model. Instead of blocking the program while waiting for an operation (like a network request, file I/O, or timer), a Future allows the program to continue executing other code and be notified when the result is ready.

Futures can be in one of two states: uncompleted or completed. When completed, they either complete with a value or with an error.

Creating a Future

There are several ways to create a Future in Dart:

    • Future.value – creates a Future that completes immediately with a given value.
    • Future.error – creates a Future that completes immediately with an error.
    • Future.delayed – creates a Future that completes after a specified duration.
    • Future.sync – creates a Future that runs a function synchronously and then completes with its result (or error).
    • Using async functions – any async function automatically returns a Future.
DARTRead-only
1
void main() {
  // Immediate value
  Future<int> immediate = Future.value(42);
  
  // Immediate error
  Future<String> error = Future.error('Something went wrong');
  
  // Delayed completion
  Future<String> delayed = Future.delayed(Duration(seconds: 2), () => 'Data loaded');
  
  // Using async function
  Future<int> asyncFuture() async {
    return 42;
  }
}

Using then() to Handle Completion

The most basic way to handle a Future's result is with the .then() method. It takes a callback that will be called with the value when the Future completes successfully.

DARTRead-only
1
void main() {
  Future<String> future = Future.delayed(Duration(seconds: 1), () => 'Hello');
  
  future.then((value) {
    print(value); // Hello
  });
  
  print('Waiting...');
}

Handling Errors with catchError

You can handle errors by chaining a .catchError() callback. It works similarly to a catch block.

DARTRead-only
1
void main() {
  Future<String> future = Future.error(Exception('Network error'));
  
  future
    .then((value) => print(value))
    .catchError((error) {
      print('Caught error: $error');
    });
}

Using whenComplete for Cleanup

The .whenComplete() callback runs regardless of whether the Future completes with a value or an error. It's useful for cleanup actions (like closing a file or hiding a loading spinner).

DARTRead-only
1
void main() {
  Future<String> future = Future.delayed(Duration(seconds: 1), () => 'Data');
  
  future
    .then((value) => print(value))
    .catchError((error) => print(error))
    .whenComplete(() => print('Done'));
}

Chaining Futures

You can chain multiple asynchronous operations by returning a Future from a .then() callback. This avoids nested callbacks (callback hell).

DARTRead-only
1
Future<String> fetchUserId() {
  return Future.delayed(Duration(seconds: 1), () => 'user123');
}

Future<String> fetchUserData(String id) {
  return Future.delayed(Duration(seconds: 1), () => 'Data for $id');
}

void main() {
  fetchUserId()
    .then((id) => fetchUserData(id))
    .then((data) => print(data))
    .catchError((error) => print(error));
}

Working with Multiple Futures

Dart provides several static methods for working with multiple Futures:

    • Future.wait – waits for all Futures to complete, then gives a list of results. If any Future completes with an error, wait completes with that error.
    • Future.any – completes with the result of the first Future that completes (success or error).
    • Future.doWhile – performs an action repeatedly until it returns false.
DARTRead-only
1
void main() async {
  var future1 = Future.delayed(Duration(seconds: 2), () => 'First');
  var future2 = Future.delayed(Duration(seconds: 1), () => 'Second');
  var future3 = Future.error('Error');

  // Future.wait
  Future.wait([future1, future2])
    .then((results) => print(results)) // ['First', 'Second']
    .catchError((e) => print(e));

  // Future.any
  Future.any([future1, future2])
    .then((first) => print(first)); // 'Second' (completes first)
}

Async/Await: A Cleaner Alternative

While you can use .then() and .catchError(), the async/await syntax is often more readable. An async function returns a Future, and await pauses the function until the Future completes. Error handling is done with try/catch.

DARTRead-only
1
Future<void> main() async {
  try {
    String id = await fetchUserId();
    String data = await fetchUserData(id);
    print(data);
  } catch (e) {
    print('Error: $e');
  }
}

Future<String> fetchUserId() async {
  await Future.delayed(Duration(seconds: 1));
  return 'user123';
}

Future<String> fetchUserData(String id) async {
  await Future.delayed(Duration(seconds: 1));
  return 'Data for $id';
}

Key Takeaways

    • A Future represents a value or error that will be available later.
    • Create Futures using Future.value, Future.error, Future.delayed, or by writing async functions.
    • Use .then() to handle successful completion, .catchError() for errors, and .whenComplete() for cleanup.
    • Chain Futures by returning a new Future from .then().
    • Use Future.wait to run multiple Futures concurrently and wait for all to complete.
    • Prefer async/await for cleaner, more readable asynchronous code.

Try it yourself

void main() {
  Future<String> fetchGreeting() {
    return Future.delayed(Duration(seconds: 2), () => 'Hello, Future!');
  }

  print('Fetching...');
  fetchGreeting().then((greeting) => print(greeting));
}

Test Your Knowledge

Q1
of 4

What does a Future represent?

A
A value that is always available synchronously
B
A potential value or error that will be available later
C
A collection of multiple values
D
An error handler
Q2
of 4

Which method is used to handle successful completion of a Future?

A
catchError()
B
whenComplete()
C
then()
D
complete()
Q3
of 4

What does `Future.wait` do?

A
Waits for the first Future to complete
B
Waits for all Futures to complete and returns a list of results
C
Executes Futures sequentially
D
Cancels all pending Futures
Q4
of 4

How do you create a Future that completes immediately with the value 42?

A
Future(42)
B
Future.value(42)
C
Future.delayed(42)
D
Future.complete(42)

Frequently Asked Questions

What is the difference between `Future` and `Stream`?

A Future represents a single value (or error) that will be available at some point. A Stream represents a sequence of multiple values (or errors) delivered over time.

Can a Future complete more than once?

No, a Future can only complete once – either with a value or with an error. Once completed, its state is final.

What happens if I don't handle an error on a Future?

Unhandled errors on a Future may cause the program to terminate (if they reach the top level). In Flutter, they can cause the app to crash. Always handle errors, either with .catchError() or with try/catch when using await.

How do I create a Future that completes after a delay?

Use Future.delayed(Duration(seconds: n), () => result).

Is `async`/`await` slower than using `.then()`?

No, async/await is syntactic sugar that compiles to the same underlying mechanism. There is no performance penalty.

Can I cancel a Future?

Dart's Future does not have a built‑in cancellation mechanism. For cancelable operations, consider using packages like dart_async or design your code so that the Future's operation can be aborted (e.g., using a Completer).

Previous

dart async await

Next

dart stream

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.