ES2017 Async/Await: Finally, Readable Asynchronous Code
ES2017 is officially out, and async/await is the feature I’m most excited about. It makes asynchronous code actually readable.
The Evolution
Callbacks (2010):
getUser(userId, function(err, user) {
if (err) return handleError(err);
getOrders(user.id, function(err, orders) {
if (err) return handleError(err);
// Callback hell
});
});
Promises (2015):
getUser(userId)
.then(user => getOrders(user.id))
.then(orders => processOrders(orders))
.catch(handleError);
Async/Await (2017):
async function processUserOrders(userId) {
try {
const user = await getUser(userId);
const orders = await getOrders(user.id);
await processOrders(orders);
} catch (error) {
handleError(error);
}
}
The async/await version reads like synchronous code but is fully asynchronous.
How It Works
The async keyword makes a function return a Promise:
async function fetchData() {
return "data";
}
// Equivalent to:
function fetchData() {
return Promise.resolve("data");
}
The await keyword pauses execution until the Promise resolves:
async function getData() {
const result = await fetch('/api/data');
const json = await result.json();
return json;
}
Error Handling
Use try/catch, just like synchronous code:
async function fetchUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error('User not found');
}
return await response.json();
} catch (error) {
console.error('Failed to fetch user:', error);
throw error;
}
}
Parallel Execution
Don’t await sequentially if you don’t need to:
// Bad - takes 6 seconds
async function fetchAll() {
const users = await fetchUsers(); // 2 seconds
const products = await fetchProducts(); // 2 seconds
const orders = await fetchOrders(); // 2 seconds
return {users, products, orders};
}
// Good - takes 2 seconds
async function fetchAll() {
const [users, products, orders] = await Promise.all([
fetchUsers(),
fetchProducts(),
fetchOrders()
]);
return {users, products, orders};
}
Real-World Example
Here’s how we refactored our API client:
Before (Promises):
function createOrder(userId, items) {
return getUser(userId)
.then(user => {
if (!user.active) {
throw new Error('User not active');
}
return validateItems(items);
})
.then(validatedItems => {
return calculateTotal(validatedItems);
})
.then(total => {
return chargeCard(userId, total);
})
.then(charge => {
return saveOrder(userId, items, charge.id);
})
.catch(error => {
logError(error);
throw error;
});
}
After (Async/Await):
async function createOrder(userId, items) {
try {
const user = await getUser(userId);
if (!user.active) {
throw new Error('User not active');
}
const validatedItems = await validateItems(items);
const total = await calculateTotal(validatedItems);
const charge = await chargeCard(userId, total);
const order = await saveOrder(userId, items, charge.id);
return order;
} catch (error) {
logError(error);
throw error;
}
}
Much more readable!
Common Mistakes
1. Forgetting async
// Wrong - await only works in async functions
function getData() {
const data = await fetch('/api/data'); // SyntaxError!
}
// Right
async function getData() {
const data = await fetch('/api/data');
}
2. Not Handling Errors
// Wrong - unhandled promise rejection
async function fetchData() {
const data = await fetch('/api/data');
return data.json();
}
// Right
async function fetchData() {
try {
const data = await fetch('/api/data');
return await data.json();
} catch (error) {
console.error('Fetch failed:', error);
throw error;
}
}
3. Sequential When You Want Parallel
// Wrong - slow
async function getAll() {
const a = await getA(); // Wait
const b = await getB(); // Wait
return [a, b];
}
// Right - fast
async function getAll() {
const [a, b] = await Promise.all([getA(), getB()]);
return [a, b];
}
Browser Support
Async/await is supported in:
- Chrome 55+
- Firefox 52+
- Safari 10.1+
- Edge 14+
For older browsers, use Babel to transpile.
Should You Use It?
Yes! If you’re using Babel (and you probably are), start using async/await today. It makes asynchronous code so much easier to read and maintain.
We’ve converted most of our Promise-based code to async/await, and the codebase is much cleaner.
What About Generators?
Generators (function*) can also handle async code, but async/await is cleaner and more intuitive. Stick with async/await unless you have a specific need for generators.
The Future
Async/await is the future of asynchronous JavaScript. Combined with Promises, it gives us the best of both worlds: the power of Promises with the readability of synchronous code.
If you haven’t tried it yet, give it a shot. You’ll never want to go back to callback hell or Promise chains.
Questions? Let me know!