Dart Future
last modified May 29, 2026
In this article we show how to work with futures in Dart language.
Future
A Future represents a potential value, or error, that will be available at some time in the future.
A Future can be complete with a value or with an error. Programmers can plug
callbacks for each case. Futures and async and await
keywords are used to perform asynchronous operations in Dart.
Common asynchronous operations include:
- fetching data over network
- reading data from a database
- reading data from a file
- performing a long-running task in a UI program
The async and await keywords provide a declarative way
to define asynchronous functions and use their results. We mark an asynchronous
function with the async keyword. The await keyword is
used to get the completed result of an asynchronous expression. The await
keyword only works within an asynchronous function.
Some languages use the term Promise for the same functionality.
Dart future simple example
The Future.value creates a future completed with value.
void main() async {
final myfut1 = Future.value(14);
print(myfut1);
final myfut2 = await Future.value(14);
print(myfut2);
}
The example contrasts a Future printed without await against one
resolved with await.
void main() async {
The main function is marked async because it uses the
await keyword inside.
final myfut1 = Future.value(14); print(myfut1);
Without await, myfut1 holds the unresolved
Future<int> object itself, not the integer value. Printing it
outputs the type description rather than 14.
var myfut2 = await Future.value(14); print(myfut2);
With await, execution suspends until the future completes and
myfut2 receives the resolved value 14.
$ dart main.dart Instance of 'Future<int>' 14
Dart Future.delayed
The Future.delayed creates a future that runs its computation after
a delay.
void main() async {
Future.delayed(Duration(seconds: 2), () => 12).then((value) => print(value));
final res = await Future.delayed(Duration(seconds: 2), () => 14);
print(res);
}
The example shows two ways to consume a delayed future: the callback style with
then and the async/await style.
Future.delayed(Duration(seconds: 2), () => 12).then((value) => print(value));
Future.delayed schedules the computation () => 12
after a two-second delay. The chained then registers a callback
that runs once the future completes, printing the resolved value.
final res = await Future.delayed(Duration(seconds: 2), () => 14); print(res);
The await keyword suspends the function until the delayed future
resolves, then assigns the result to res. Both futures start at
roughly the same time, so the total wait is about two seconds, not four.
$ dart main.dart 12 14
Dart read file with readAsString
The readAsString function reads the entire file contents as a
string asynchronously. It returns a Future<String> that
completes with the string once the file contents has been read.
bear fruit cloud sky forest falcon wood lake rock
This is the words.txt file.
import 'dart:io';
void main() async {
final file = File('words.txt');
final contents = await file.readAsString();
print(contents);
}
The example reads the words.txt file asynchronously and prints its
full content. The await suspends main until the read
completes, after which the string is available in contents.
Dart Future.wait
The Future.wait waits for multiple futures to complete and collects
their results. It returns a future which completes once all the given futures
have completed.
import 'dart:math';
Future<int> getRandomValue() async {
await Future.delayed(Duration(seconds: 1));
final random = Random();
return random.nextInt(150);
}
int findMaxVal(List<int> lst) {
lst.forEach((e) => print(e));
return lst.reduce(max);
}
void main() async {
final maximum = await Future.wait([
getRandomValue(),
getRandomValue(),
getRandomValue(),
getRandomValue(),
getRandomValue(),
getRandomValue()
]).then((List<int> results) => findMaxVal(results));
print('Maximum is : $maximum');
}
In the example, getRandomValue simulates an async operation by
waiting one second before returning a random integer. We launch it six times and
pass all futures to Future.wait, which resolves only after every
future completes. The then callback receives the collected results
and finds the maximum value.
$ dart main.dart 94 13 106 41 110 122 Maximum is : 122
In the following example we check for the existence of a directory.
import 'dart:io';
void main() async {
final path = 'doc/crypto';
final dr = Directory(path);
final present = await dr.exists();
if (present) {
print('directory exists');
Future.wait([dr.delete()]).then((_) => checkIfExists(dr));
} else {
print('directory does not exist');
}
}
void checkIfExists(Directory dr) async {
final present = await dr.exists();
print(present ? 'directory exists' : 'directory does not exist');
}
If the given directory exists, we delete it, then check for its existence again.
Future.wait([dr.delete()]).then((_) => checkIfExists(dr));
With Future.wait we wait for the directory to be deleted. Only
after the asynchronous operation finishes, we call checkIfExists.
Dart future get request
The http is a composable, Future-based library for making HTTP
requests.
$ dart pub add http
We add the http package.
name: app
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
http: ^1.2.0
This is the pubspec.yaml.
import 'package:http/http.dart' as http;
Future<String> fetchData() async {
final resp = await http.get(Uri.http('webcode.me'));
if (resp.statusCode == 200) {
return resp.body;
} else {
throw Exception('Failed to fetch data');
}
}
void main() async {
final data = await fetchData();
print(data);
}
In the example, we send a GET request to the webcode.me and
print the response body.
$ dart main.dart <!DOCTYPE html> <html lang="en"> <head> ...
Dart Future error handling
Errors thrown inside a future propagate through the chain, bypassing
then callbacks until a catchError handler is
reached. With async/await, a standard
try/catch block handles future errors instead.
void main() async {
Future(() => throw 'something went wrong')
.then((_) => print('never reached'))
.catchError((e) => print('caught: $e'));
}
The error thrown inside the future skips the then callback
entirely and is delivered to catchError.
$ dart main.dart caught: something went wrong
The equivalent code using async/await with a
try/catch block:
Future<int> divide(int a, int b) async {
if (b == 0) throw ArgumentError('division by zero');
return a ~/ b;
}
void main() async {
try {
final result = await divide(10, 0);
print(result);
} on ArgumentError catch (e) {
print('caught: $e');
}
}
Using try/catch with await is
generally preferred over catchError because it supports typed
error handling with on clauses and reads as linear, synchronous
code.
$ dart main.dart caught: Invalid argument(s): division by zero
Dart Future chaining
Futures can be chained with then, where each callback receives
the result of the previous future and may return a new one. The
async/await style expresses the same logic in a
linear, easier-to-read form.
Future<String> fetchUser(int id) async {
await Future.delayed(Duration(milliseconds: 100));
return 'user_$id';
}
Future<String> fetchRole(String user) async {
await Future.delayed(Duration(milliseconds: 100));
return '$user: admin';
}
void main() async {
// chaining style
await fetchUser(1)
.then((user) => fetchRole(user))
.then(print);
// async/await style
final user = await fetchUser(2);
final role = await fetchRole(user);
print(role);
}
Both styles produce the same result. Chaining is concise for short pipelines,
while async/await is clearer when the logic grows or
requires intermediate variables and error handling.
$ dart main.dart user_1: admin user_2: admin
Dart Future.any
Future.any accepts a list of futures and completes with the result
of whichever future finishes first. The remaining futures continue running but
their results are discarded.
Future<String> fetchFrom(String name, int ms) async {
await Future.delayed(Duration(milliseconds: ms));
return 'response from $name';
}
void main() async {
final result = await Future.any([
fetchFrom('server A', 400),
fetchFrom('server B', 150),
fetchFrom('server C', 600),
]);
print(result);
}
Three requests are issued in parallel. Future.any resolves as
soon as the fastest one completes — here server B after 150 ms. This
pattern is useful for redundant requests where only the fastest response
matters. If all futures fail, Future.any completes with the last
error.
$ dart main.dart response from server B
Dart Future timeout
The timeout method enforces a time limit on a future. If the
future does not complete within the given duration, a
TimeoutException is thrown. An optional onTimeout
callback can return a fallback value instead.
import 'dart:async';
Future<String> slowOperation() async {
await Future.delayed(Duration(seconds: 5));
return 'done';
}
void main() async {
try {
final result = await slowOperation()
.timeout(Duration(seconds: 2));
print(result);
} on TimeoutException {
print('operation timed out');
}
}
slowOperation takes five seconds to complete, but the two-second
timeout fires first. The TimeoutException is caught
by the try block.
$ dart main.dart operation timed out
Dart Completer
A Completer gives manual control over a future's completion. You
create a completer, expose its future property to callers, and
resolve or reject it at any point with complete or
completeError. This is useful for bridging callback-based APIs
into the future model.
import 'dart:async';
Future<int> computeValue() {
final completer = Completer<int>();
Future.delayed(Duration(seconds: 1), () {
completer.complete(42);
});
return completer.future;
}
void main() async {
final value = await computeValue();
print('computed: $value');
}
final completer = Completer<int>();
A Completer<int> is created. Its future
property is an unresolved Future<int> that callers can
await.
completer.complete(42);
After one second the completer is resolved with 42, waking any
awaiting code. Use completer.completeError(e) to reject the
future with an error. A completer may only be completed once; calling
complete or completeError a second time throws a
StateError.
$ dart main.dart computed: 42
Dart microtask queue
Dart's event loop uses two queues: the microtask queue and the
event queue. Microtasks have higher priority — they drain completely
before the event loop picks up the next event. Futures created with
Future() place their callbacks on the event queue, while
scheduleMicrotask and the continuations of
async/await expressions go onto the microtask queue.
The execution order is:
- synchronous code runs to completion
- microtask queue drains completely
- one event is taken from the event queue, then microtasks drain again
import 'dart:async';
void main() {
print('A');
Future(() => print('B')); // event queue
scheduleMicrotask(() => print('C')); // microtask queue
print('D');
}
print('A');
...
print('D');
Synchronous statements run first, so A and D are
printed before any async work starts.
scheduleMicrotask(() => print('C'));
After the synchronous code finishes, the microtask queue drains. The
scheduleMicrotask callback runs and prints C.
Future(() => print('B'));
Only after all microtasks are processed does the event loop take the next item
from the event queue, printing B last.
$ dart main.dart A D C B
Source
Dart Future - language reference
In this article we have worked with futures in Dart.
Author
List all Dart tutorials.