← Back

PROMISES IN JAVASCRIPT

A promise is an object that stores the state of an asynchronous operation.

This note explains promises in simple language.

You will learn:

  1. what a promise is
  2. what promise states are
  3. how to create a promise
  4. what resolve and reject do
  5. how then() works
  6. how catch() works
  7. how finally() works
  8. what promise chains are

1. What is a promise?

A promise is an object that stores the state of an asynchronous operation.

It is like a wrapper for a result that is not ready yet when the promise is created.

Instead of getting the final value immediately, you get a promise that the value will appear later.

Easy real-life idea

Imagine someone promises to bake a cake in two weeks.

A JavaScript promise works in a similar way.

Diagram 1. Main idea of a promise

Start async operation
-> result is not ready yet
-> JavaScript gives you a Promise object
-> later you get success or error

A promise is not the final value. A promise is an object that represents the future result of an asynchronous operation.

2. Promise states

A promise can be in three states:

Pending

This is the initial state. The promise was created, but the asynchronous operation has not finished yet.

Fulfilled

The operation finished successfully, and the promise received a value.

Rejected

The operation failed, and the promise received an error.

Diagram 2. Promise lifecycle

Promise created
-> Pending
   |- success -> Fulfilled
   `- error   -> Rejected

A promise always starts as pending. Later it becomes either fulfilled or rejected. After that, it stays in that state forever.

3. What does "settled" mean?

When a promise is no longer pending, it is called settled.

That means:

Important: settled is not a separate state. It is just a common word for a finished promise.

Diagram 3. Settled meaning

Pending
-> Fulfilled or Rejected
-> Settled

4. Creating a promise

A promise is created with:

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

The Promise class takes a function called the executor. This function runs immediately when the promise is created.

Inside it, you usually start some asynchronous work.

When that work finishes, you call:

Diagram 4. Promise creation

new Promise((resolve, reject) => {
  ...
})

resolve(value) -> fulfilled
reject(error)  -> rejected

resolve means success. reject means failure.

5. Example of creating a promise

const isSuccess = true;

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    if (isSuccess) {
      resolve("Success! Value passed to resolve function");
    } else {
      reject("Error! Error passed to reject function");
    }
  }, 2000);
});

console.log(promise);

In this example:

Diagram 5. Example flow

Promise created
-> Pending
-> wait 2 seconds
-> Check isSuccess
   |- true  -> resolve(...) -> Fulfilled
   `- false -> reject(...)  -> Rejected

6. The then() method

After creating a promise, you need a way to handle its result.

For that, use:

promise.then(onResolve, onReject);

The then() method takes two callback functions:

Meaning:

Diagram 6. then() logic

promise.then(onResolve, onReject)

Fulfilled -> onResolve(value)
Rejected  -> onReject(error)

7. Example of then()

const isSuccess = true;

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    if (isSuccess) {
      resolve("Success! Value passed to resolve function");
    } else {
      reject("Error! Error passed to reject function");
    }
  }, 2000);
});

promise.then(
  value => {
    console.log(value);
  },
  error => {
    console.log(error);
  }
);

If the promise succeeds, the first callback runs.

If the promise fails, the second callback runs.

Diagram 7. then() example

Promise settles
|- fulfilled -> first callback
`- rejected  -> second callback

8. Named functions in then()

If the callback logic becomes bigger, it is often better to create separate functions and pass them into then() by name.

That improves readability.

Example idea

const onSuccess = value => {
  console.log(value);
};

const onError = error => {
  console.log(error);
};

promise.then(onSuccess, onError);

9. The catch() method

In real code, then() is usually used only for success, and errors are handled separately with catch().

promise
  .then(value => {
    // success
  })
  .catch(error => {
    // error
  });

The catch() method runs only if the promise is rejected. Its callback receives the error. It should come after then().

Diagram 8. then() + catch()

promise
-> then(...)  = success path
-> catch(...) = error path

This is the most common promise pattern in practice.

10. Example of catch()

const isSuccess = true;

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    if (isSuccess) {
      resolve("Success! Value passed to resolve function");
    } else {
      reject("Error! Error passed to reject function");
    }
  }, 2000);
});

promise
  .then(value => {
    console.log(value);
  })
  .catch(error => {
    console.log(error);
  });

If the promise is fulfilled, catch() does not run.

If the promise is rejected, catch() runs and receives the error.

Diagram 9. catch() meaning

Promise rejected
-> catch(error) runs

11. The finally() method

Sometimes you need code that must run no matter what happened.

For that, use:

promise
  .then(...)
  .catch(...)
  .finally(() => {
    // always runs
  });

The finally() method runs after the promise is settled, whether it was fulfilled or rejected. Its callback receives no arguments.

Diagram 10. finally() logic

Promise settles
|- fulfilled
`- rejected
-> finally() runs in both cases

Use finally() for logic that should always happen, for example:

12. Example of finally()

const isSuccess = true;

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    if (isSuccess) {
      resolve("Success! Value passed to resolve function");
    } else {
      reject("Error! Error passed to reject function");
    }
  }, 2000);
});

promise
  .then(value => console.log(value))
  .catch(error => console.log(error))
  .finally(() => console.log("Promise settled"));

Here:

13. Promise chains

A very important feature: then() returns a new promise.

That makes it possible to build chains.

Each next then() gets the value returned by the previous then() callback.

Example

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(5);
  }, 2000);
});

promise
  .then(value => {
    console.log(value); // 5
    return value * 2;
  })
  .then(value => {
    console.log(value); // 10
    return value * 3;
  })
  .then(value => {
    console.log(value); // 30
  })
  .catch(error => {
    console.log(error);
  })
  .finally(() => {
    console.log("finally");
  });

In this chain:

Diagram 11. Promise chain flow

resolve(5)
-> then(value = 5)
   return 10
-> then(value = 10)
   return 30
-> then(value = 30)

Each then() passes its returned value forward to the next promise in the chain.

14. What happens if there is an error in the chain?

If an error appears anywhere in the chain:

That is why catch() is usually placed at the end of the whole chain.

Diagram 12. Error in chain

then(...)
-> then(...)
-> error happens
-> remaining then(...) are skipped
-> catch(error)
-> finally()

15. Important rules to remember

A promise starts as pending

That is always the first state.

A promise becomes fulfilled or rejected

After that, it cannot change again.

then() handles success

The first callback of then() receives the resolved value.

catch() handles errors

It runs only for rejected promises or errors in the chain.

finally() always runs

It runs after settlement and gets no arguments.

then() returns a new promise

That is why chaining is possible.

Diagram 13. Easy memory map

Promise
|
|- Pending
|- Fulfilled
`- Rejected

then()    -> success
catch()   -> error
finally() -> always

16. Common beginner mistakes

Mistake 1. Thinking a promise is a function

A promise is an object, not a function.

Mistake 2. Thinking settled is a separate state

It is not a separate state. It only means fulfilled or rejected.

Mistake 3. Thinking catch() runs after success

It does not. It runs only when there is an error or rejection.

Mistake 4. Thinking finally() gets the value or error

It does not receive any arguments.

Mistake 5. Forgetting that then() returns a new promise

This is why promise chains work.

17. Easy memory rules

Promise = future result of async work

Pending   = still working
Fulfilled = success
Rejected  = error

resolve() = success
reject()  = failure

then()    = handle success
catch()   = handle error
finally() = run always

18. Quick summary

19. Final conclusion

If you understand these ideas:

promise
pending
fulfilled
rejected
resolve
reject
then
catch
finally
chain

then you already have a strong foundation for working with promises in JavaScript.

← Back