adnenre
#JavaScript

Mastering JavaScript Promises: A Complete Guide

Learn how to work with Promises in JavaScript for handling asynchronous operations

#Mastering JavaScript Promises: A Complete Guide

Promises are a fundamental concept in modern JavaScript, providing a clean and powerful way to handle asynchronous operations. They help you write more readable and maintainable code compared to traditional callback patterns.

#Why Use Promises?

#Benefits of Promises

  • Cleaner Code - Avoid “callback hell” with readable chainable syntax
  • Better Error Handling - Centralized error handling with .catch()
  • Composition - Combine multiple async operations easily
  • Predictable State - Promises have well-defined states

#Promise States

stateDiagram-v2
    [*] --> Pending
    Pending --> Fulfilled : resolve()
    Pending --> Rejected : reject()
    Fulfilled --> [*] : .then()
    Rejected --> [*] : .catch() or .then()

#Getting Started

#Basic Promise Creation

// Creating a simple Promise
const fetchData = new Promise((resolve, reject) => {
  setTimeout(() => {
    const success = Math.random() > 0.5;

    if (success) {
      resolve({ data: 'Operation successful!', status: 200 });
    } else {
      reject(new Error('Failed to fetch data'));
    }
  }, 1000);
});

// Using the Promise
fetchData
  .then((response) => {
    console.log('Success:', response.data);
  })
  .catch((error) => {
    console.error('Error:', error.message);
  })
  .finally(() => {
    console.log('Request completed');
  });

#Promise Chaining Pattern

graph TD
    A[Start Request] --> B[fetchUser]
    B --> C[processUser]
    C --> D[updateDatabase]
    D --> E[Send Response]
    E --> F[End]

    style A fill:#e1f5e1,stroke:#333,stroke-width:2px
    style F fill:#e1f5e1,stroke:#333,stroke-width:2px
// Real-world chaining example
function getUser(userId) {
  return new Promise((resolve) => {
    setTimeout(
      () =>
        resolve({
          id: userId,
          name: 'Alice',
          role: 'admin',
        }),
      500
    );
  });
}

function getUserPermissions(role) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(['read:posts', 'write:posts', 'delete:posts']), 300);
  });
}

// Chaining promises
getUser(123)
  .then((user) => {
    console.log('User found:', user.name);
    return getUserPermissions(user.role);
  })
  .then((permissions) => {
    console.log('User permissions:', permissions);
    return {
      success: true,
      timestamp: new Date(),
    };
  })
  .then((result) => {
    console.log('Final result:', result);
  })
  .catch((error) => {
    console.error('Error in chain:', error);
  });

#Advanced Promise Techniques

#Parallel Execution

// Promise.all() - Execute multiple promises in parallel
const fetchUsers = fetch('/api/users');
const fetchPosts = fetch('/api/posts');
const fetchComments = fetch('/api/comments');

Promise.all([fetchUsers, fetchPosts, fetchComments])
  .then((responses) => Promise.all(responses.map((r) => r.json())))
  .then(([users, posts, comments]) => {
    console.log('Total users:', users.length);
    console.log('Total posts:', posts.length);
    console.log('Total comments:', comments.length);
  })
  .catch((error) => {
    console.error('One request failed:', error);
  });

// Promise.race() - Get the first settled promise
const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 5000));
const apiCall = fetch('/api/data');

Promise.race([apiCall, timeout])
  .then((response) => {
    if (response.ok) return response.json();
    throw new Error('API error');
  })
  .then((data) => console.log('Data received:', data))
  .catch((error) => console.error('Failed:', error.message));

#Async/Await Syntax

graph LR
    A[Async Function] --> B[Await Promise]
    B --> C[Handle Result]
    C --> D[Return Value]
    D --> E[Promise Resolved]

    style A fill:#e1f5fe,stroke:#333,stroke-width:2px
    style E fill:#e1f5fe,stroke:#333,stroke-width:2px
// Modern async/await pattern
async function loadUserProfile(userId) {
  try {
    // Sequential execution
    const user = await fetch(`/api/users/${userId}`);
    const posts = await fetch(`/api/users/${userId}/posts`);
    const comments = await fetch(`/api/users/${userId}/comments`);

    const [userData, postsData, commentsData] = await Promise.all([
      user.json(),
      posts.json(),
      comments.json(),
    ]);

    return {
      profile: userData,
      activity: {
        posts: postsData,
        comments: commentsData,
      },
    };
  } catch (error) {
    console.error('Failed to load profile:', error);
    throw error;
  }
}

// Using the async function
loadUserProfile(123)
  .then((profile) => console.log('Profile loaded:', profile))
  .catch((error) => console.error('Error:', error));

#Best Practices

#1. Always Handle Errors

// Good
fetch('/api/data')
  .then((response) => response.json())
  .catch((error) => console.error('Fetch failed:', error));

// Bad (unhandled rejection)
fetch('/api/data').then((response) => response.json());

#2. Return Promises Properly

// Good - returns promise for chaining
function getUserData(id) {
  return fetch(`/api/users/${id}`).then((response) => response.json());
}

// Use it
getUserData(123)
  .then((user) => console.log(user))
  .catch((error) => console.error(error));

#3. Use Promise Utilities

// Helper function for timeouts
function withTimeout(promise, ms) {
  return Promise.race([
    promise,
    new Promise((_, reject) => setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms)),
  ]);
}

// Helper for retry logic
function retry(fn, retries = 3, delay = 1000) {
  return fn().catch((error) => {
    return retries > 1
      ? new Promise((resolve) => setTimeout(() => resolve(retry(fn, retries - 1, delay)), delay))
      : Promise.reject(error);
  });
}

#Common Patterns

#Loading States

// Track loading state
let isLoading = true;
let data = null;
let error = null;

fetch('/api/data')
  .then((response) => response.json())
  .then((result) => {
    data = result;
  })
  .catch((err) => {
    error = err;
  })
  .finally(() => {
    isLoading = false;
    // Update UI
  });

#Sequential vs Parallel

sequenceDiagram
    participant user
    participant [example](example.com)
    participant iframe
    participant ![viewscreen](./.tiny-icon.png)
    user->>dotcom: Go to the [example](example.com) page
    dotcom->>iframe: loads html w/ iframe url
    iframe->>viewscreen: request template
    viewscreen->>iframe: html & javascript
    iframe->>dotcom: iframe ready
    dotcom->>iframe: set mermaid data on iframe
    iframe->>iframe: render mermaid
// Sequential (one after another)
async function sequential() {
  const user = await getUser();
  const posts = await getUserPosts(user.id); // Waits for user first
  const comments = await getPostComments(posts[0].id); // Waits for posts
}

// Parallel (all at once)
async function parallel() {
  const [user, posts, comments] = await Promise.all([
    getUser(),
    getUserPosts(123),
    getPostComments(456),
  ]);
}

#Next Steps

  1. Practice with real API calls using fetch()
  2. Explore async/await patterns for cleaner code
  3. Learn about Promise composition patterns
  4. Study error handling strategies for production apps

#Resources

Happy coding! 🚀

Share this post