in Dart, Futures are all about handling asynchronous operations in a structured way. When we say Futures are chained, it means we’re connecting multiple async operations so that one runs only after the previous one completes.
Every time a Future completes, it can trigger another Future using .then(), .catchError(), or .whenComplete(). This chaining helps to avoid deeply nested callbacks and gives a clean, readable flow of async tasks.
Let’s take a simple example first:
Future getUser() {
return Future.delayed(Duration(seconds: 2), () => "User fetched");
}
Future getOrders(String user) {
return Future.delayed(Duration(seconds: 2), () => "Orders for $user");
}
void main() {
getUser()
.then((user) {
print(user);
return getOrders(user); // chain next Future
})
.then((orders) {
print(orders);
})
.catchError((error) {
print("Error occurred: $error");
})
.whenComplete(() {
print("All tasks done");
});
}
Here’s what happens step by step:
getUser()runs first and returns a Future.- Once that Future completes, the
.then()block runs and callsgetOrders(user). - The second
.then()waits forgetOrders()to finish before executing. - If any error occurs in the chain,
.catchError()will handle it. - Finally,
.whenComplete()runs whether there was success or error.
In one of my projects, I used Future chaining when making sequential API calls — for example, fetching user info, then fetching related data based on that user, and finally saving results locally. Instead of writing nested awaits, I chained them with .then() for cleaner flow and better control over error handling.
A challenge I faced was error propagation — if you forget to return a Future inside a .then(), the chain breaks and later .then() blocks might execute too early. So it’s important to always return the Future inside the chain.
A limitation of chaining with .then() is that it can still become less readable when the sequence is long. That’s why in most modern Dart code, we prefer using async/await syntax — it internally works the same way as Future chaining but looks synchronous and is easier to read.
In short, Future chaining in Dart is like linking multiple async steps together — each one runs after the previous completes, making it perfect for sequential async workflows.
