Using Async and Await
Learn how to write asynchronous code using async/await in modern programming languages.
A Simple Analogy
Imagine ordering coffee at a busy café. Instead of watching the barista make your coffee while blocking everyone behind you, they take your order and give you a number. You sit down, chat with friends, or check your phone while they make it. When your number is called, you pick it up. Async/await is like that—your code doesn't block waiting; it moves on to other work and checks back when the task is done.
What Are Async and Await?
async and await are keywords that allow you to write asynchronous code in a way that looks synchronous. They make it easy to handle long-running operations (database queries, API calls, file reads) without blocking your application.
async: Declares that a function will handle asynchronous operationsawait: Pauses execution until a Promise/Task completes, then continues
Why Use Async/Await?
- Non-blocking: Your app can do other work while waiting (serving other requests, UI stays responsive)
- Readable: Looks like synchronous code but runs asynchronously
- Better UX: Apps respond quickly instead of freezing
- Scalability: Handle thousands of concurrent requests with fewer resources
- Error handling: Use try/catch just like synchronous code
How It Works (Simplified)
1. Call async function
2. Hit await keyword
3. Function pauses, returns control
4. Meanwhile, other code runs
5. When task completes, function resumes
6. Result is returned
Async/Await in JavaScript
Basic Example
// Without async/await (old style with .then())
function fetchUser(id) {
return fetch(`/api/users/${id}`)
.then(res => res.json())
.then(data => console.log(data))
.catch(error => console.error(error));
}
// With async/await (modern, cleaner)
async function fetchUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
}
// Call it
await fetchUser(1);
Multiple Awaits (Sequence)
async function getOrderDetails(orderId) {
try {
const order = await fetchOrder(orderId); // Wait for order
const items = await fetchOrderItems(orderId); // Then wait for items
const user = await fetchUser(order.userId); // Then wait for user
return { order, items, user };
} catch (error) {
console.error(error);
}
}
Parallel Execution (Faster)
async function getOrderDetails(orderId) {
try {
// Fetch all three at the same time
const [order, items, user] = await Promise.all([
fetchOrder(orderId),
fetchOrderItems(orderId),
fetchUser(order.userId),
]);
return { order, items, user };
} catch (error) {
console.error(error);
}
}
Async/Await in C# / .NET
Basic Example
// Async method
public async Task<User> GetUserAsync(int id)
{
try
{
var response = await _httpClient.GetAsync($"/api/users/{id}");
var json = await response.Content.ReadAsStringAsync();
var user = JsonConvert.DeserializeObject<User>(json);
return user;
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
return null;
}
}
// Calling it
var user = await GetUserAsync(1);
In a Controller
[HttpGet("{id}")]
public async Task<IActionResult> GetUser(int id)
{
var user = await _userService.GetUserAsync(id);
if (user == null)
return NotFound();
return Ok(user);
}
Task vs. Task
// No return value
public async Task LogEventAsync(string message)
{
await _logger.WriteAsync(message);
}
// With return value
public async Task<int> CountUsersAsync()
{
return await _db.Users.CountAsync();
}
Async/Await in Python
import asyncio
async def fetch_user(user_id):
"""Async function"""
await asyncio.sleep(1) # Simulate API call
return {"id": user_id, "name": "Alice"}
async def main():
user = await fetch_user(1)
print(user)
# Run it
asyncio.run(main())
Concurrent Tasks
async def main():
# Run tasks in parallel
results = await asyncio.gather(
fetch_user(1),
fetch_user(2),
fetch_user(3),
)
print(results)
asyncio.run(main())
Common Patterns
Sequential (Wait for Each)
async function process() {
const result1 = await task1();
const result2 = await task2(result1); // Depends on result1
const result3 = await task3(result2);
return result3;
}
Parallel (All at Once)
async function process() {
const [r1, r2, r3] = await Promise.all([
task1(),
task2(),
task3(),
]);
return [r1, r2, r3];
}
Race (First to Finish)
async function fetchWithTimeout() {
const result = await Promise.race([
fetchData(),
timeout(5000), // 5 second timeout
]);
return result;
}
Real-World Use Cases
- Web APIs: Wait for database query, then send response
- File uploads: Upload multiple files in parallel
- Microservices: Call multiple services, combine results
- UI interactions: Fetch data without freezing buttons/scroll
- Data processing: Handle multiple tasks concurrently
Best Practices
- Always
awaitasync functions (don't call without await unless intentional) - Use
try/catchfor error handling - Use
Promise.all()for parallel tasks - Avoid nested awaits when possible (refactor for clarity)
- Don't create async functions unless they actually use
await - Be careful with async in loops (use
for...ofnot.forEach()) - Remember:
awaitonly works insideasyncfunctions
Common Pitfalls
Forgetting Await (Returns Promise, Not Value)
// Wrong - user is a Promise, not the actual data
const user = getUserAsync(1);
// Correct
const user = await getUserAsync(1);
Sequential When You Need Parallel
// Slow (waits 3 seconds total)
const r1 = await fetch1(); // 1s
const r2 = await fetch2(); // 1s
const r3 = await fetch3(); // 1s
// Fast (waits 1 second total)
const [r1, r2, r3] = await Promise.all([
fetch1(),
fetch2(),
fetch3(),
]);
Async in Loops
// Wrong (slow, sequential)
users.forEach(async user => {
await processUser(user); // Wait one at a time
});
// Better (parallel)
await Promise.all(users.map(user => processUser(user)));
Related Concepts to Explore
- Promises and Promise chaining
- Event loops and concurrency models
- Callbacks and callback hell
- Generators and iterators
- Reactive programming (RxJS)
- Coroutines and fibers
- Thread safety and synchronization
- Deadlocks and race conditions
- Throttling and debouncing
- Stream processing
Summary
Async/await transformed asynchronous programming from callback hell to readable, maintainable code. By understanding how to properly use async/await, sequence operations, run tasks in parallel, and handle errors, you can build responsive applications that scale—whether it's handling thousands of web requests or keeping your UI smooth while processing data.