Back to all posts

Promises in JavaScript


JavaScript is inherently single-threaded, but it needs to handle time-consuming operation like network requests, file reading, and database queries without blocking the main thread. This challenge led to the fascinating evolution of asynchronous programming technique.

The Problem with Traditional Callbacks

function fetchUserData(userId, callback) {
    $.ajax({
        url: '/api/users/' + userId,
        success: function(userData) {
            fetchUserPosts(userData.id, function(posts) {
                fetchPostComments(posts[0].id, function(comments) {
                    renderPage(userData, posts, comments);
                }, function(error) {
                    console.error('Failed to fetch comments');
                });
            }, function(error) {
                console.error('Failed to fetch posts');
            });
        },
        error: function(error) {
            console.error('Failed to fetch user data');
        }
    });
}

This code demonstrates the infamous “calback hell”

  • Deeply nested function calls
  • Complex error handing
  • Reduced readability
  • Difficult to manage sequential or parallel operations

Classic Countdown Example: Callback Chaos

console.log("3…");
setTimeout(() => {
    console.log("2…");
    setTimeout(() => {
        console.log("1…");
        setTimeout(() => {
            console.log("Happy New Year!!");
        }, 1000);
    }, 1000);
}, 1000);

Promises

Promises introduced a more structured approach to handing asynchronous operations:

function fetchUserData(userId) {
    return new Promise((resolve, reject) => {
        $.ajax({
            url: '/api/users/' + userId,
            success: (userData) => resolve(userData),
            error: (error) => reject(error)
        });
    });
}

// Promise chaining
fetchUserData(123)
    .then(userData => fetchUserPosts(userData.id))
    .then(posts => {
        console.log('User posts:', posts);
    })
    .catch(error => {
        console.error('An error occurred:', error);
    });

Promises Methods

1. Promises.all()

async function fetchMultipleResources() {
    try {
        const [users, posts, comments] = await Promise.all([
            fetchUsers(),
            fetchPosts(),
            fetchComments()
        ]);
    } catch (error) {
        console.error('One request failed');
    }
}

2. Promises.race()

const fastest = await Promise.race([
    fetch('https://api1.com/data'),
    fetch('https://api2.com/data'),
    new Promise((_, reject) => 
        setTimeout(() => reject(new Error('Timeout')), 5000)
    )
]);

Async/Await

async function getUserContent(userId) {
    try {
        const userData = await fetchUserData(userId);
        const posts = await fetchUserPosts(userData.id);
        const comments = await fetchPostComments(posts[0].id);
        
        return { userData, posts, comments };
    } catch (error) {
        console.error('Failed to fetch user content:', error);
    }
}

Refactored Countdown Example

function createDelay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function newYearCountdown() {
    console.log("3…");
    await createDelay(1000);
    
    console.log("2…");
    await createDelay(1000);
    
    console.log("1…");
    await createDelay(1000);
    
    console.log("Happy New Year!!");
}

Parallel vs Sequential Execution

// Parallel Execution
async function fetchParallel() {
    const [users, posts] = await Promise.all([
        fetchUsers(),
        fetchPosts()
    ]);
}

// Sequential Execution
async function fetchSequential() {
    const users = await fetchUsers();
    const posts = await fetchPosts(users[0].id);
}

Best Practices

  1. Always Handle Errors
    • Use .catch() or try/catch blocks
    • Provide meaningful error messages

2. Avoid Unnecessary Promise Wrapping

// Bad
function bad() {
    return new Promise(resolve => resolve(42));
}

// Good
function good() {
    return Promise.resolve(42);
}

Use Async/Await for Most Use Cases

  • Looks like synchronous code
  • More readable
  • Easier error handling

Read More: https://www.joshwcomeau.com/javascript/promises/