Tutorials / Web Development

JavaScript Async Patterns - Promises, Async/Await, and Advanced Techniques

Mahesh Mahesh Waghmare
3 min read Intermediate

Asynchronous programming is fundamental to modern JavaScript. This guide covers Promises, async/await, error handling, and advanced patterns for managing asynchronous operations.

Introduction to Async JavaScript

JavaScript is single-threaded but handles asynchronous operations through callbacks, Promises, and async/await.

Why Async?

  • Non-blocking operations
  • Better user experience
  • Efficient resource usage
  • Handling I/O operations

Evolution:

  • Callbacks → Promises → Async/Await

Understanding Promises

Creating Promises

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('Success!');
    }, 1000);
});

Promise Methods

then/catch:

fetch('/api/data')
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error(error));

Promise.all:

const promises = [
    fetch('/api/users'),
    fetch('/api/posts'),
    fetch('/api/comments'),
];

Promise.all(promises)
    .then(responses => Promise.all(responses.map(r => r.json())))
    .then(([users, posts, comments]) => {
        console.log({ users, posts, comments });
    });

Promise.allSettled:

Promise.allSettled(promises)
    .then(results => {
        results.forEach((result, index) => {
            if (result.status === 'fulfilled') {
                console.log(`Promise ${index} succeeded`);
            } else {
                console.log(`Promise ${index} failed`);
            }
        });
    });
Advertisement

Async/Await Syntax

Basic Async Function

async function fetchData() {
    const response = await fetch('/api/data');
    const data = await response.json();
    return data;
}

Sequential vs Concurrent

Sequential:

async function sequential() {
    const user = await fetchUser();
    const posts = await fetchPosts(user.id);
    const comments = await fetchComments(posts[0].id);
    return { user, posts, comments };
}

Concurrent:

async function concurrent() {
    const [user, posts, comments] = await Promise.all([
        fetchUser(),
        fetchPosts(),
        fetchComments(),
    ]);
    return { user, posts, comments };
}

Error Handling

Try/Catch

async function fetchData() {
    try {
        const response = await fetch('/api/data');
        if (!response.ok) {
            throw new Error('Network response was not ok');
        }
        const data = await response.json();
        return data;
    } catch (error) {
        console.error('Error:', error);
        throw error;
    }
}

Error Boundaries

async function safeFetch(url) {
    try {
        const response = await fetch(url);
        return { data: await response.json(), error: null };
    } catch (error) {
        return { data: null, error: error.message };
    }
}
Advertisement

Concurrent Operations

Parallel Execution

async function processItems(items) {
    const results = await Promise.all(
        items.map(item => processItem(item))
    );
    return results;
}

Limiting Concurrency

async function processWithLimit(items, limit) {
    const results = [];
    for (let i = 0; i < items.length; i += limit) {
        const batch = items.slice(i, i + limit);
        const batchResults = await Promise.all(
            batch.map(item => processItem(item))
        );
        results.push(...batchResults);
    }
    return results;
}

Advanced Patterns

Retry Pattern

async function fetchWithRetry(url, retries = 3) {
    for (let i = 0; i < retries; i++) {
        try {
            const response = await fetch(url);
            return await response.json();
        } catch (error) {
            if (i === retries - 1) throw error;
            await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
        }
    }
}

Timeout Pattern

function withTimeout(promise, ms) {
    return Promise.race([
        promise,
        new Promise((_, reject) =>
            setTimeout(() => reject(new Error('Timeout')), ms)
        ),
    ]);
}

Conclusion

JavaScript async patterns enable:

  • Non-blocking operations
  • Better error handling
  • Concurrent execution
  • Cleaner code with async/await

Key principles:

  • Use async/await for readability
  • Handle errors properly
  • Use Promise.all for concurrent operations
  • Implement retry and timeout patterns

Mastering async patterns is essential for modern JavaScript development.

Advertisement
Mahesh Waghmare

Written by Mahesh Waghmare

I bridge the gap between WordPress architecture and modern React frontends. Currently building tools for the AI era.

Follow on Twitter