On this page23 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. What async and await Do
- 2. An async Function Returns a Future
- 3. await Unwraps the Value
- 4. Future<void> main() async
- 5. await Pauses Only That Function
- 6. Rewriting a .then Chain with await
- 7. Returning Values from async Functions
- 8. await Only Inside async
- 9. await in a Loop Runs One at a Time
- 10. Forgetting await
- 11. A Real-World Example
- Code Examples
- Common Beginner Mistakes
- Best Practices
- Mini Challenge
- Practice Questions
- Practice Answers
- Quick Revision
- Key Takeaways
- What's Next?
Lecture 89 — async & await
| Category | 15 — Asynchronous Programming |
| Lecture | 089 of 140 |
| Estimated video length | 12 min |
| Target | Dart 3.12.2 |
| Level | Intermediate |
A
.thenchain works, but each step hides inside its own callback. Withasyncandawait, asynchronous code reads top to bottom like ordinary code — and still never blocks the program.
Contents
Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways
Every error message in this lecture is real. Each one was produced by running the code shown on Dart 3.12.2, so you will recognize it when you meet it yourself.
Prerequisites
Students should already understand, from Lectures 030–038, 072 and 087–088:
- functions, parameters, return values, and arrow functions
- generic types such as
List<int> - the difference between synchronous and asynchronous code
Future<T>,Future.delayed, and.thenchains
Connection With Previous Lectures
In Lecture 088, we loaded a student and then her marks with a .then chain:
Future<String> fetchStudent() {
return Future.delayed(Duration(seconds: 1), () => 'Sara');
}
Future<int> fetchMarks(String name) {
return Future.delayed(Duration(seconds: 1), () => 92);
}
void main() {
fetchStudent()
.then((name) => fetchMarks(name))
.then((marks) => print('Marks: $marks'));
}Marks: 92It works, but try printing Sara: 92. The name lives only inside the first callback; the
second callback cannot see it. Every new step adds another callback and pushes the logic
further from the plain, line-by-line code we have written since Lecture 003.
Learning Objectives
By the end of this lecture you will be able to:
- Mark a function
asyncand explain why it always returns aFuture - Use
awaitto get the value out of aFuture - Write
Future<void> main() async - Explain why
awaitpauses only its own function - Rewrite a
.thenchain usingawait - Return values from
asyncfunctions - Predict the total time of
awaitinside a loop - Recognize the errors and wrong output caused by a missing
asyncorawait
1. What async and await Do
Two keywords work together:
asyncmarks a function as asynchronous. It goes before the function body.awaitgoes in front of aFutureand means "wait here until this value is ready".
Analogy — you order tea at a café. You wait at the counter for your cup, but the café does not close while you wait: other customers keep ordering.
awaitmakes your function wait; the rest of the program keeps going.
2. An async Function Returns a Future
Mark a function async and Dart wraps whatever it returns in a Future:
Future<String> greet() async {
return 'Hello, Ali';
}
void main() {
print(greet());
}Instance of 'Future<String>'The function returns the text 'Hello, Ali', but the caller receives a Future<String> — a
promise of that text. That is why the return type is Future<String>, not String.
3. await Unwraps the Value
To get the value itself, put await in front of the call:
Future<String> greet() async {
return 'Hello, Ali';
}
Future<void> main() async {
var message = await greet();
print(message);
}Hello, Aliawait greet() waits for the Future<String> to complete and gives back a plain String.
Dart infers message as String, so you use it like any other variable.
4. Future<void> main() async
await is only allowed inside an async function, so main itself must become one. Since
every async function returns a Future, and main returns nothing, its return type is
Future<void>:
Future<double> fetchBalance() {
return Future.delayed(Duration(seconds: 1), () => 1500.0);
}
Future<void> main() async {
print('Checking balance...');
var balance = await fetchBalance();
print('Balance: $balance');
}Checking balance...
Balance: 1500.0The first line prints at once; the second appears one second later. You will write
Future<void> main() async in almost every program from now on.
5. await Pauses Only That Function
await does not freeze the whole program. Here main is not async, and it does not
wait for loadMarks:
Future<void> loadMarks() async {
print('2. Loading marks...');
await Future.delayed(Duration(seconds: 1));
print('4. Marks: 85');
}
void main() {
print('1. Start');
loadMarks();
print('3. main keeps going');
}1. Start
2. Loading marks...
3. main keeps going
4. Marks: 85loadMarks runs normally until its first await. There it pauses, and control goes back to
main, which prints line 3. One second later, loadMarks resumes where it stopped. Notice
await Future.delayed(...) with no value — waiting is all we need.
6. Rewriting a .then Chain with await
Now the problem from the start of the lecture. Each .then becomes one await line:
Future<String> fetchStudent() {
return Future.delayed(Duration(seconds: 1), () => 'Sara');
}
Future<int> fetchMarks(String name) {
return Future.delayed(Duration(seconds: 1), () => 92);
}
Future<void> main() async {
var name = await fetchStudent();
var marks = await fetchMarks(name);
print('$name: $marks');
}Sara: 92The callbacks are gone. name and marks are ordinary variables in one function, so the last
line can use both. Under the hood it is still the same Futures — await is simply a clearer
way to write them.
7. Returning Values from async Functions
Inside an async function you return the plain value. Dart delivers it through the
Future:
Future<int> addMarks(int a, int b) async {
await Future.delayed(Duration(milliseconds: 500));
return a + b;
}
Future<void> main() async {
var total = await addMarks(80, 75);
print('Total: $total');
}Total: 155return a + b returns an int; the caller gets a Future<int> and awaits it. The return
type must be a Future. Write int instead and Dart refuses:
int addMarks(int a, int b) async {
return a + b;
}Error: Functions marked 'async' must have a return type assignable to 'Future'.
int addMarks(int a, int b) async {
^^^8. await Only Inside async
Forget async on the function and await is not allowed:
Future<int> fetchScore() async {
return 40;
}
void main() {
var score = await fetchScore();
print(score);
}Error: 'await' can only be used in 'async' or 'async*' methods.
var score = await fetchScore();
^^^^^The fix is Future<void> main() async. The async* in the message is a different kind of
function — we will study it in Lecture 096 — Async Generators.
9. await in a Loop Runs One at a Time
await inside a loop waits for each Future before the next one starts. Dart's built-in
Stopwatch measures how long that takes (..start() is the cascade from Lecture 020):
Future<int> fetchPrice(String item) {
return Future.delayed(Duration(seconds: 1), () => item.length * 100);
}
Future<void> main() async {
var watch = Stopwatch()..start();
var items = ['pen', 'book', 'bag'];
var total = 0;
for (var item in items) {
var price = await fetchPrice(item);
print('$item: $price');
total += price;
}
print('Total: $total');
print('Took about ${watch.elapsed.inSeconds} seconds');
}pen: 300
book: 400
bag: 300
Total: 1000
Took about 3 secondsThree one-second waits, one after another: three seconds. That is right when each step needs the previous result. When the prices are independent, they could all load at once. We will learn to run Futures in parallel in Lecture 091 — Working with Multiple Futures.
10. Forgetting await
Leave out await and nothing crashes — you just get the wrong thing:
Future<String> fetchName() async {
return 'Zayan';
}
Future<void> main() async {
var name = fetchName();
print('Hello, $name');
}Hello, Instance of 'Future<String>'name is the Future, not the text. With a Future<void>, the mistake is about order:
Future<void> saveScore(int score) async {
await Future.delayed(Duration(seconds: 1));
print('Saved $score');
}
Future<void> main() async {
saveScore(90);
print('Done');
}Done
Saved 90main printed Done before the score was saved. Dart's analyzer can catch this. Add the
unawaited_futures rule to the project's analysis_options.yaml (Lecture 002):
linter:
rules:
- unawaited_futuresThen dart analyze reports:
info - bin/main.dart:7:3 - Missing an 'await' for the 'Future' computed by this expression. Try adding an 'await' or wrapping the expression with 'unawaited'. - unawaited_futuresWrite await saveScore(90); and Saved 90 prints first.
11. A Real-World Example
Sara orders shoes. The shop looks up the price and the stock, then applies a 10% discount:
Future<int> fetchPrice(String item) {
return Future.delayed(Duration(milliseconds: 500), () => 1200);
}
Future<int> fetchStock(String item) {
return Future.delayed(Duration(milliseconds: 500), () => 3);
}
Future<double> placeOrder(String customer, String item, int quantity) async {
print('Placing order for $customer...');
var price = await fetchPrice(item);
var stock = await fetchStock(item);
if (quantity > stock) {
print('Only $stock left.');
return 0;
}
var total = price * quantity * 0.9;
print('$quantity x $item at $price, 10% off');
return total;
}
Future<void> main() async {
var total = await placeOrder('Sara', 'Shoes', 2);
print('Total: $total');
var second = await placeOrder('Ali', 'Shoes', 5);
print('Total: $second');
}Placing order for Sara...
2 x Shoes at 1200, 10% off
Total: 2160.0
Placing order for Ali...
Only 3 left.
Total: 0.0placeOrder awaits two Futures, makes an ordinary if decision, and returns a double —
which its callers receive as a Future<double> and await.
Code Examples
Example 1 — async Returns a Future
Future<int> fetchAge() async {
return 20;
}
Future<void> main() async {
print(fetchAge());
print(await fetchAge());
}Instance of 'Future<int>'
20Example 2 — Awaiting a Delayed Value
Future<double> fetchTemperature() {
return Future.delayed(Duration(seconds: 1), () => 31.5);
}
Future<void> main() async {
print('Reading sensor...');
var temperature = await fetchTemperature();
print('Temperature: $temperature');
}Reading sensor...
Temperature: 31.5Example 3 — Only the async Function Pauses
Future<void> download() async {
print('Download started');
await Future.delayed(Duration(seconds: 1));
print('Download finished');
}
void main() {
download();
print('Zayan keeps playing');
}Download started
Zayan keeps playing
Download finishedExample 4 — Same Result, Two Styles
Future<String> fetchCity() {
return Future.delayed(Duration(milliseconds: 500), () => 'Lahore');
}
Future<void> withThen() {
return fetchCity().then((city) => print('With then: $city'));
}
Future<void> withAwait() async {
var city = await fetchCity();
print('With await: $city');
}
Future<void> main() async {
await withThen();
await withAwait();
}With then: Lahore
With await: LahoreExample 5 — Returning a Value After Waiting
Future<String> grade(int marks) async {
await Future.delayed(Duration(milliseconds: 500));
return marks >= 50 ? 'Pass' : 'Fail';
}
Future<void> main() async {
print('Ali: ${await grade(72)}');
print('Sara: ${await grade(45)}');
}Ali: Pass
Sara: Failawait works inside ${} too — it is an expression like any other.
Example 6 — Awaiting a Future<void>
Future<void> saveScore(int score) async {
await Future.delayed(Duration(seconds: 1));
print('Saved $score');
}
Future<void> main() async {
await saveScore(90);
print('Done');
}Saved 90
DoneExample 7 — Student Marks
Future<int> fetchMark(String subject) {
var marks = {'Math': 80, 'Science': 75, 'English': 85};
return Future.delayed(Duration(milliseconds: 300), () => marks[subject]!);
}
Future<void> main() async {
var math = await fetchMark('Math');
var science = await fetchMark('Science');
var english = await fetchMark('English');
print('Student: Ali');
print('Total: ${math + science + english}');
}Student: Ali
Total: 240Example 8 — Shopping Total
Future<int> fetchPrice(String item) {
var prices = {'Notebook': 500, 'Pen': 50, 'Bag': 1500};
return Future.delayed(Duration(milliseconds: 300), () => prices[item]!);
}
Future<void> main() async {
var cart = ['Notebook', 'Pen', 'Bag'];
var total = 0;
for (var item in cart) {
var price = await fetchPrice(item);
print('$item: $price');
total += price;
}
print('Total: $total');
}Notebook: 500
Pen: 50
Bag: 1500
Total: 2050Example 9 — Bank Balance
Future<double> deposit(double balance, double amount) async {
await Future.delayed(Duration(milliseconds: 300));
return balance + amount;
}
Future<double> withdraw(double balance, double amount) async {
await Future.delayed(Duration(milliseconds: 300));
return balance - amount;
}
Future<void> main() async {
var balance = 1000.0;
balance = await deposit(balance, 250.5);
balance = await withdraw(balance, 100);
print('Balance: $balance');
}Balance: 1150.5The withdrawal must wait for the deposit's result, so awaiting one after the other is exactly right here.
Example 10 — Temperature Conversion
Future<double> fetchCelsius(String city) {
return Future.delayed(Duration(milliseconds: 500), () => 25.0);
}
Future<double> toFahrenheit(String city) async {
var celsius = await fetchCelsius(city);
return celsius * 9 / 5 + 32;
}
Future<void> main() async {
var fahrenheit = await toFahrenheit('Karachi');
print('Karachi: $fahrenheit °F');
}Karachi: 77.0 °FOne async function awaits another, and main awaits the result.
Common Beginner Mistakes
1. Printing the Future instead of its value
Future<void> main() async {
// var name = fetchName(); // ✗ name is a Future<String>
var name = await fetchName();
print('Hello, $name');
}The wrong version prints Hello, Instance of 'Future<String>'.
2. Using await in a function that is not async
void main() {
// var score = await fetchScore(); // ✗
}Error: 'await' can only be used in 'async' or 'async*' methods.Write Future<void> main() async.
3. Giving an async function a non-Future return type
// int addMarks(int a, int b) async { return a + b; } // ✗Error: Functions marked 'async' must have a return type assignable to 'Future'.Write Future<int>, and still return the plain int.
4. Calling a Future<void> function without await
Future<void> main() async {
// saveScore(90); // ✗ "Done" prints first
await saveScore(90);
print('Done');
}No error appears — only the wrong order. Turn on unawaited_futures to catch it.
5. Thinking await freezes the whole program
await pauses only the function it is written in. Callers that do not await it keep running,
as Step 5 showed.
Best Practices
- Prefer
async/awaitover.thenchains — the code reads in the order it runs. - Use
Future<void> main() asyncwhenevermainneeds to wait for something. - Write the full return type:
Future<int>,Future<String>,Future<void>. - Await every
Futurewhose result or completion you depend on. - Enable the
unawaited_futureslint so a forgottenawaitis reported. - Await in a loop only when each step needs the previous one; otherwise run them in parallel (Lecture 091).
Mini Challenge
Zayan's rocket counts down from 3, waiting one second between numbers, then launches. Write
an async function countdown(int from) and call it from main.
Your program should print exactly:
Zayan's rocket
3
2
1
Liftoff!Try it yourself before opening the solution.
Show solution
Future<void> countdown(int from) async {
for (var i = from; i >= 1; i--) {
print(i);
await Future.delayed(Duration(seconds: 1));
}
}
Future<void> main() async {
print("Zayan's rocket");
await countdown(3);
print('Liftoff!');
}Zayan's rocket
3
2
1
Liftoff!Without await in main, Liftoff! would print right after 3.
Practice Questions
Work through these in order — they run from beginner to application to challenge.
Beginner
-
Write an
asyncfunctiongetCity()that returns'Karachi'. Await it inmainand print the result. -
What does this program print, and why?
Future<String> getCity() async { return 'Karachi'; } void main() { print(getCity()); } -
This program does not compile. Fix
mainso it prints40:Future<int> fetchScore() async { return 40; } void main() { var score = await fetchScore(); print(score); }
Application
-
Write
square(int n)that waits 500 milliseconds and returnsn * n. Print the square of7. -
Rewrite this using
await, with the same output:Future<int> fetchPrice() { return Future.delayed(Duration(seconds: 1), () => 450); } void main() { fetchPrice().then((price) => print('Price: $price')); } -
Predict the exact output order:
Future<void> task() async { print('B'); await Future.delayed(Duration(milliseconds: 500)); print('D'); } void main() { print('A'); task(); print('C'); } -
greet(name)takes one second and returns'Hello, $name'. Greet Ali, Sara, and Zayan in afor-inloop withawait, then print how many seconds it took. -
Fix the return type so this compiles and prints
155:int total(int a, int b) async { return a + b; }
Challenge
-
Sara's account starts at
2000.0. Writeasyncfunctionsdepositandchargethat each wait 300 milliseconds and return the new balance. Deposit500, charge a fee of12.5, and printBalance: 2487.5. -
This program prints
All donebefore the report is saved. Fix it, and explain in one sentence what was wrong:Future<void> saveReport(String name) async { await Future.delayed(Duration(seconds: 1)); print('Saved report for $name'); } Future<void> main() async { saveReport('Sara'); print('All done'); }
Practice Answers
Question 1 — Solution
Future<String> getCity() async {
return 'Karachi';
}
Future<void> main() async {
var city = await getCity();
print(city);
}KarachiQuestion 2 — Solution
Instance of 'Future<String>'An async function always returns a Future. Without await, print receives the Future
itself, not the text inside it.
Question 3 — Solution
Future<int> fetchScore() async {
return 40;
}
Future<void> main() async {
var score = await fetchScore();
print(score);
}40Question 4 — Solution
Future<int> square(int n) async {
await Future.delayed(Duration(milliseconds: 500));
return n * n;
}
Future<void> main() async {
print(await square(7));
}49Question 5 — Solution
Future<int> fetchPrice() {
return Future.delayed(Duration(seconds: 1), () => 450);
}
Future<void> main() async {
var price = await fetchPrice();
print('Price: $price');
}Price: 450Question 6 — Solution
A
B
C
Dtask prints B before its first await, then pauses. main does not wait, so it prints
C. Half a second later task resumes and prints D.
Question 7 — Solution
Future<String> greet(String name) {
return Future.delayed(Duration(seconds: 1), () => 'Hello, $name');
}
Future<void> main() async {
var watch = Stopwatch()..start();
for (var name in ['Ali', 'Sara', 'Zayan']) {
print(await greet(name));
}
print('Took about ${watch.elapsed.inSeconds} seconds');
}Hello, Ali
Hello, Sara
Hello, Zayan
Took about 3 secondsQuestion 8 — Solution
Future<int> total(int a, int b) async {
return a + b;
}
Future<void> main() async {
print(await total(80, 75));
}155Question 9 — Solution
Future<double> deposit(double balance, double amount) async {
await Future.delayed(Duration(milliseconds: 300));
return balance + amount;
}
Future<double> charge(double balance, double fee) async {
await Future.delayed(Duration(milliseconds: 300));
return balance - fee;
}
Future<void> main() async {
var balance = 2000.0;
balance = await deposit(balance, 500);
balance = await charge(balance, 12.5);
print('Balance: $balance');
}Balance: 2487.5Question 10 — Solution
saveReport was called without await, so main moved on before the save finished.
Future<void> saveReport(String name) async {
await Future.delayed(Duration(seconds: 1));
print('Saved report for $name');
}
Future<void> main() async {
await saveReport('Sara');
print('All done');
}Saved report for Sara
All doneQuick Revision
- What does an
asyncfunction always return? - What does
awaitgive you back from aFuture<int>? - Why is
mainwrittenFuture<void> main() async? - Does
awaitpause the whole program? - What prints if you interpolate a
Futurewithout awaiting it? - Three one-second awaits in a loop take about how long?
- Which lint reports a forgotten
await?
Key Takeaways
asyncmarks a function as asynchronous; it always returns aFuture.- Inside it,
returnthe plain value — the caller receivesFuture<T>. awaitwaits for aFutureand gives back its value.awaitis only allowed insideasyncfunctions, somainbecomesFuture<void> main() async.awaitpauses only its own function; the code before the firstawaitruns at once.- Every
.thenstep becomes oneawaitline, with all values in one scope. awaitin a loop runs the steps one after another; their times add up.- A forgotten
awaitgivesInstance of 'Future<…>'or the wrong order — enableunawaited_futures.
What's Next?
Lecture 090 — Future Error Handling. Every Future in this lecture succeeded. Real ones
fail: the network drops, an item is out of stock, a file is missing. Next we will see what
happens when an awaited Future completes with an error, and how the try/catch from
Lecture 084 works around await.