DEV Community

Dev Cookies
Dev Cookies

Posted on

🌟 Understanding JavaScript Promises with Code Examples

JavaScript is a single-threaded, asynchronous language. This can often make managing asynchronous operations (like API calls, timers, or file I/O) a challenge. Enter Promises β€” a powerful tool introduced in ES6 to handle asynchronous tasks in a cleaner and more manageable way.

In this blog, we'll break down what promises are, how to use them, and walk through code examples using both .then()/.catch() and async/await.


πŸ” What is a Promise?

A Promise is an object representing the eventual completion or failure of an asynchronous operation.

A promise has three states:

  • Pending: Initial state, neither fulfilled nor rejected.
  • Fulfilled: The operation completed successfully.
  • Rejected: The operation failed.

βœ… Basic Promise Example

Let’s create a simple promise that simulates fetching data from a server using setTimeout.

const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const success = true;

      if (success) {
        resolve("βœ… Data fetched successfully!");
      } else {
        reject("❌ Failed to fetch data.");
      }
    }, 1000);
  });
};

fetchData()
  .then((response) => {
    console.log("Success:", response);
  })
  .catch((error) => {
    console.error("Error:", error);
  });
Enter fullscreen mode Exit fullscreen mode

Output:

Success: βœ… Data fetched successfully!
Enter fullscreen mode Exit fullscreen mode

πŸ”„ Using async/await with Promises

The async/await syntax makes writing and reading asynchronous code easier and more intuitive.

const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const success = true;

      if (success) {
        resolve("βœ… Data fetched successfully!");
      } else {
        reject("❌ Failed to fetch data.");
      }
    }, 1000);
  });
};

const getData = async () => {
  try {
    const data = await fetchData();
    console.log("Result:", data);
  } catch (error) {
    console.error("Error:", error);
  }
};

getData();
Enter fullscreen mode Exit fullscreen mode

πŸ”— Promise Chaining

You can chain multiple .then() calls to run a sequence of asynchronous operations:

const stepOne = () => {
  return new Promise((resolve) => {
    setTimeout(() => resolve("Step 1 completed"), 500);
  });
};

const stepTwo = (prev) => {
  return new Promise((resolve) => {
    setTimeout(() => resolve(`${prev} β†’ Step 2 completed`), 500);
  });
};

stepOne()
  .then(stepTwo)
  .then((result) => {
    console.log("Chain Result:", result);
  });
Enter fullscreen mode Exit fullscreen mode

🧡 Running Multiple Promises in Parallel

Use Promise.all() when you want to run multiple promises in parallel and wait for all of them to complete:

const task = (name, delay) =>
  new Promise((resolve) => {
    setTimeout(() => resolve(`${name} done`), delay);
  });

Promise.all([
  task("Task 1", 1000),
  task("Task 2", 500),
  task("Task 3", 1500),
]).then((results) => {
  console.log("All tasks done:", results);
});
Enter fullscreen mode Exit fullscreen mode

Output:

All tasks done: ["Task 1 done", "Task 2 done", "Task 3 done"]
Enter fullscreen mode Exit fullscreen mode

⚠️ Error Handling

Always handle errors with .catch() or try...catch when using async/await:

const riskyOperation = () =>
  new Promise((_, reject) => {
    setTimeout(() => reject("Something went wrong!"), 1000);
  });



riskyOperation()
  .then((res) => console.log(res))
  .catch((err) => console.error("Caught Error:", err));
Enter fullscreen mode Exit fullscreen mode

🧠 Conclusion

Promises make working with asynchronous code in JavaScript much more readable and robust. Whether you're chaining multiple calls or using async/await, they allow you to write cleaner, more maintainable code.

πŸ”— Key Takeaways:

  • Promises can be in one of three states: pending, fulfilled, or rejected.
  • Use .then()/.catch() for chaining or async/await for cleaner async code.
  • Promise.all() runs multiple promises in parallel.
  • Always handle promise rejections gracefully.

Part 2

Handling APIs using JavaScript Promises is one of the most common tasks in web development. Here's a complete breakdown with a blog-style explanation and code examples using:

  1. fetch() with .then()/.catch()
  2. async/await
  3. Centralized API handling with reusable Promise-based functions

πŸ“‘ How to Handle APIs Using JavaScript Promises

JavaScript Promises allow us to work with asynchronous APIs in a clean, readable way. When we call an API (e.g., using fetch()), it returns a promise β€” a placeholder for a future value.


πŸ”Ή 1. Using fetch() with .then() and .catch()

fetch("https://jsonplaceholder.typicode.com/posts/1")
  .then((response) => {
    if (!response.ok) {
      throw new Error("API error: " + response.status);
    }
    return response.json(); // Convert response to JSON
  })
  .then((data) => {
    console.log("βœ… API Data:", data);
  })
  .catch((error) => {
    console.error("❌ Error fetching API:", error.message);
  });
Enter fullscreen mode Exit fullscreen mode

βœ… Output:

{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati",
  "body": "quia et suscipit..."
}
Enter fullscreen mode Exit fullscreen mode

πŸ”Ή 2. Using async/await (Modern & Cleaner)

const getPost = async () => {
  try {
    const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");

    if (!response.ok) {
      throw new Error("API error: " + response.status);
    }

    const data = await response.json();
    console.log("βœ… API Data:", data);
  } catch (error) {
    console.error("❌ Error fetching API:", error.message);
  }
};

getPost();
Enter fullscreen mode Exit fullscreen mode

πŸ”Ή 3. Reusable Promise-Based API Function

Let’s build a centralized function to handle GET, POST, PUT, DELETE requests using Promises.

const apiRequest = (url, method = "GET", body = null) => {
  return new Promise((resolve, reject) => {
    fetch(url, {
      method,
      headers: {
        "Content-Type": "application/json",
      },
      body: body ? JSON.stringify(body) : null,
    })
      .then((response) => {
        if (!response.ok) {
          throw new Error("API error: " + response.status);
        }
        return response.json();
      })
      .then(resolve)
      .catch(reject);
  });
};

// πŸ”Ή GET Example
apiRequest("https://jsonplaceholder.typicode.com/posts/1")
  .then((data) => console.log("GET Success:", data))
  .catch((err) => console.error("GET Error:", err.message));

// πŸ”Ή POST Example
apiRequest("https://jsonplaceholder.typicode.com/posts", "POST", {
  title: "New Post",
  body: "This is the body",
  userId: 1,
})
  .then((data) => console.log("POST Success:", data))
  .catch((err) => console.error("POST Error:", err.message));
Enter fullscreen mode Exit fullscreen mode

🧠 Best Practices for Handling APIs with Promises

  • βœ… Always handle errors using .catch() or try...catch.
  • πŸ”„ Use async/await for cleaner, more readable async code.
  • ♻️ Use reusable functions to reduce code duplication.
  • 🚦 Check response.ok before parsing the response.

πŸ§ͺ Bonus: Parallel API Requests using Promise.all()

const urls = [
  "https://jsonplaceholder.typicode.com/posts/1",
  "https://jsonplaceholder.typicode.com/posts/2",
];

Promise.all(urls.map((url) => fetch(url).then((res) => res.json())))
  .then((responses) => {
    console.log("βœ… All API Responses:", responses);
  })
  .catch((err) => {
    console.error("❌ One of the APIs failed:", err.message);
  });
Enter fullscreen mode Exit fullscreen mode

🏁 Conclusion

Using Promises with fetch() allows us to make API calls cleanly and handle errors gracefully. With async/await, the code becomes even easier to understand. Wrapping your API logic in reusable Promise-based functions can save time and reduce bugs.

Top comments (0)