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!