Skip to main content

JavaScript Promises Explained for Beginners

Updated 4 min readMarkdown

JavaScript is single-threaded, but it can still handle asynchronous operations like API calls, file reading, and timers without freezing the application. Earlier, developers mostly used callbacks for handling asynchronous tasks. However, callbacks often made code difficult to read and maintain.

This is where Promises were introduced in JavaScript.

Promises provide a cleaner and more structured way to handle asynchronous operations.

What Problem Do Promises Solve?

Before promises, asynchronous code was commonly written using callbacks.

Example Using Callbacks

console.log("Start");

setTimeout(() => {
  console.log("Data fetched");

  setTimeout(() => {
    console.log("Data processed");

    setTimeout(() => {
      console.log("Finished");
    }, 1000);

  }, 1000);

}, 1000);

This style creates deeply nested code known as Callback Hell.

Problems with callbacks:

  • Hard to read

  • Difficult to debug

  • Error handling becomes messy

  • Code maintenance becomes harder

Promises solve these issues by making asynchronous code cleaner and more organized.

What is a Promise?

A Promise is an object that represents a value that may be available:

  • now

  • later

  • or never

You can think of a promise as a future value.

For example:

“I promise I will deliver the result later.”

The result can either:

  • succeed

  • or fail

Promise States

A Promise has three states:

  1. Pending

  2. Fulfilled

  3. Rejected

Promise Lifecycle

           Pending
          /       \
         /         \
 Fulfilled       Rejected
   (Success)      (Failure)

1. Pending

Initial state of a promise.

The operation is still running.

const promise = new Promise((resolve, reject) => {
  // operation running
});

2. Fulfilled

The operation completed successfully.

resolve("Data received");

3. Rejected

The operation failed.

reject("Something went wrong");

Creating a Basic Promise

Syntax

const myPromise = new Promise((resolve, reject) => {

  let success = true;

  if (success) {
    resolve("Operation successful");
  } else {
    reject("Operation failed");
  }

});

Handling Success and Failure

Promises use:

  • .then() → for success

  • .catch() → for errors

Example

const myPromise = new Promise((resolve, reject) => {

  let success = true;

  if (success) {
    resolve("Data fetched successfully");
  } else {
    reject("Failed to fetch data");
  }

});

myPromise
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.log(error);
  });

Basic Promise Lifecycle Example

console.log("Start");

const promise = new Promise((resolve, reject) => {

  setTimeout(() => {
    resolve("Task completed");
  }, 2000);

});

promise.then((message) => {
  console.log(message);
});

console.log("End");

Output

Start
End
Task completed

Explanation:

  • JavaScript does not wait for the asynchronous task

  • The promise runs in the background

  • Once completed, .then() executes

Promise Chaining Concept

One of the biggest advantages of promises is chaining.

Instead of nesting callbacks, we can chain .then() methods.

Example

const promise = new Promise((resolve, reject) => {

  resolve(2);

});

promise
  .then((num) => {
    console.log(num);
    return num * 2;
  })
  .then((num) => {
    console.log(num);
    return num * 3;
  })
  .then((num) => {
    console.log(num);
  })
  .catch((error) => {
    console.log(error);
  });

Output:

2
4
12

Each .then() receives the value returned from the previous .then().

This makes asynchronous workflows much cleaner.

Callback vs Promise Comparison

Callback Example

getUser(function(user) {

  getPosts(user.id, function(posts) {

    getComments(posts[0].id, function(comments) {

      console.log(comments);

    });

  });

});

Promise Example

getUser()
  .then((user) => {
    return getPosts(user.id);
  })
  .then((posts) => {
    return getComments(posts[0].id);
  })
  .then((comments) => {
    console.log(comments);
  })
  .catch((error) => {
    console.log(error);
  });

Why Promises Improve Readability

Promises improve readability because:

  • Code becomes flatter

  • Error handling is centralized

  • Logic becomes easier to follow

  • Asynchronous workflows become predictable

Instead of deeply nested callbacks, promises allow sequential execution in a clean way.

Real-World Example: Fetching API Data

fetch("https://jsonplaceholder.typicode.com/users")
  .then((response) => {
    return response.json();
  })
  .then((data) => {
    console.log(data);
  })
  .catch((error) => {
    console.log("Error:", error);
  });

This is one of the most common uses of promises in modern JavaScript applications.

Advantages of Promises

  • Better readability

  • Easier error handling

  • Avoid callback hell

  • Cleaner asynchronous code

  • Supports chaining

  • Works well with async/await

Limitations of Promises

  • Can still become complex in large applications

  • Multiple chained .then() calls may reduce readability

  • Understanding asynchronous behavior takes practice

Diagram Ideas for Your Blog

1. Promise Lifecycle Diagram

           Pending
          /       \
         /         \
 Fulfilled       Rejected

2. Callback vs Promise Flow

Callback

Task 1
  └── Task 2
       └── Task 3
            └── Task 4

Promise

Task 1 → Task 2 → Task 3 → Task 4

Learn JavaScript Promises step by step with simple examples. Understand promise states, lifecycle, chaining, error handling, and how promises solve callback hell in asynchronous JavaScript.

No comments yet. Be the first to comment.