Follow AiTechWorlds on LinkedIn for professional AI content!Follow Now →
22 minLesson 23 of 35
Asynchronous JavaScript

Promises: Creating & Consuming

Promises: Creating & Consuming

Promises are JavaScript's solution to asynchronous operations — fetching data, reading files, querying databases. Understanding Promises deeply is essential because they underpin async/await, React data fetching, and virtually all modern JavaScript.

What Is a Promise?

A Promise is an object representing an operation that will complete (or fail) in the future. It has three states:

  • Pending — operation in progress
  • Fulfilled — operation completed successfully (has a value)
  • Rejected — operation failed (has a reason/error)
// A Promise is created with a function that receives resolve and reject
const myPromise = new Promise((resolve, reject) => {
    // Do async work here...
    const success = true;
    
    if (success) {
        resolve("Operation succeeded!");  // Fulfills the promise
    } else {
        reject(new Error("Something went wrong"));  // Rejects the promise
    }
});

// State transitions:
// pending → fulfilled (when resolve is called)
// pending → rejected  (when reject is called)
// Once settled, state never changes

Consuming Promises with .then() / .catch()

fetch("https://api.github.com/users/octocat")
    .then(response => response.json())   // .then() runs on success
    .then(user => {
        console.log(user.name);
        console.log(user.followers);
    })
    .catch(error => {                    // .catch() runs on any error
        console.error("Failed:", error.message);
    })
    .finally(() => {                     // .finally() always runs
        console.log("Request finished");
    });

Promise Chaining

Each .then() returns a new Promise, allowing chains:

function getUser(userId) {
    return fetch(`/api/users/${userId}`).then(r => r.json());
}

function getUserPosts(user) {
    return fetch(`/api/posts?userId=${user.id}`).then(r => r.json());
}

// Chain — each step gets the result of the previous
getUser(1)
    .then(user => {
        console.log(`User: ${user.name}`);
        return getUserPosts(user);    // Return a new Promise
    })
    .then(posts => {
        console.log(`Posts: ${posts.length}`);
    })
    .catch(error => console.error(error));

Key rule: If you return a value from .then(), the next .then() gets that value. If you return a Promise, the next .then() waits for it to resolve.

Creating Promises for Real Use Cases

// Wrap a callback-based API in a Promise
function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

function readFile(path) {
    return new Promise((resolve, reject) => {
        fs.readFile(path, 'utf8', (error, data) => {
            if (error) reject(error);
            else resolve(data);
        });
    });
}

// Promisify a database query
function queryDB(sql, params) {
    return new Promise((resolve, reject) => {
        db.query(sql, params, (err, results) => {
            if (err) reject(err);
            else resolve(results);
        });
    });
}

Running Promises in Parallel

// Promise.all — run multiple promises concurrently, wait for ALL
const [users, products, orders] = await Promise.all([
    fetch('/api/users').then(r => r.json()),
    fetch('/api/products').then(r => r.json()),
    fetch('/api/orders').then(r => r.json()),
]);
// All three requests run at the same time — much faster than sequential

// If ANY promise rejects, Promise.all rejects immediately
Promise.all([
    Promise.resolve(1),
    Promise.reject(new Error("Failed!")),  // This causes immediate rejection
    Promise.resolve(3),
])
.catch(err => console.error(err.message));  // "Failed!"
// Promise.allSettled — wait for ALL, even if some fail
const results = await Promise.allSettled([
    fetch('/api/users').then(r => r.json()),
    fetch('/api/broken-endpoint').then(r => r.json()),
    fetch('/api/products').then(r => r.json()),
]);

results.forEach(result => {
    if (result.status === 'fulfilled') {
        console.log('Success:', result.value);
    } else {
        console.log('Failed:', result.reason);
    }
});
// Use this when you want results from as many requests as possible
// Promise.race — resolves/rejects with the FIRST to settle
// Common use: timeouts
function withTimeout(promise, ms) {
    const timeout = new Promise((_, reject) =>
        setTimeout(() => reject(new Error('Request timed out')), ms)
    );
    return Promise.race([promise, timeout]);
}

withTimeout(fetch('/api/slow-endpoint'), 5000)
    .then(r => r.json())
    .catch(err => console.error(err.message));  // "Request timed out" if > 5s

Error Handling with Promises

// Errors propagate through the chain until caught
fetch('/api/users')
    .then(response => {
        if (!response.ok) {
            throw new Error(`HTTP error: ${response.status}`);  // Triggers .catch()
        }
        return response.json();
    })
    .then(users => {
        return users.map(user => processUser(user));  // Error here also goes to .catch()
    })
    .catch(error => {
        // Handles errors from ANY step above
        console.error("Something failed:", error.message);
        return [];  // Return fallback — chain continues with []
    })
    .then(result => {
        console.log("Final result:", result);
    });

The Common Pitfall: Forgetting to Return

// BUG: forgetting return in .then()
fetch('/api/user')
    .then(response => {
        response.json();   // No return! Next .then() gets undefined
    })
    .then(user => {
        console.log(user);  // undefined — not the user object!
    });

// FIX:
fetch('/api/user')
    .then(response => response.json())  // Arrow function implicitly returns
    .then(user => {
        console.log(user);  // The actual user object ✓
    });

Promise vs Async/Await

Promises and async/await are the same thing — async/await is just syntax sugar over Promises:

// With Promises
function getUser(id) {
    return fetch(`/api/users/${id}`)
        .then(r => r.json())
        .catch(e => console.error(e));
}

// With async/await (same behavior)
async function getUser(id) {
    try {
        const response = await fetch(`/api/users/${id}`);
        return await response.json();
    } catch (e) {
        console.error(e);
    }
}

async/await reads more like synchronous code and is generally preferred for new code. Understanding Promises first makes async/await obvious.

Next lesson: async/await — writing clean asynchronous code that reads like synchronous code.

📱

Get this course's notes on Telegram!

Free cheat sheets, summaries & practice exercises

Get Notes Free →
!