Promises

October 7, 2024

Promises

Alright, so now we are going to learn about promises. A promise is an object that represents the eventual completion or failure of an asynchronous operation. The concept is that a promise is made to complete some kind of task or operation, such as fetching data from a server. Meanwhile, the rest of the code continues to execute. So it's asynchronous and non-blocking. When the task is complete, the promise is either fulfilled or rejected. It also prevents callback hell, which are multiple nested callbacks, as we saw in the previous video.

Most of the time, until you get into more advanced JavaScript, you will be dealing with the response from promises, not writing them. For instance, using the fetch API will return a promise. So you will need to know what to do with it. In this video, I will show you how to deal with them but also how to create them with the Promise constructor.

I'm going to show you how to refactor our posts code from callbacks to promises in the next video, but first I want to give you a super simple example of creating and dealing with promises, so that you can understand the syntax and the concept in general.

Creating a Promise

We use the Promise constructor to create a new promise. The Promise constructor takes in a function that has two parameters, resolve and reject. The resolve function is called when the promise is successful and the reject function is called when the promise is not successful.

Let's create a simple promise:

const promise = new Promise(function (resolve, reject) {
  // Do an async task
  setTimeout(function () {
    console.log('Async task complete');
    resolve();
  }, 1000);
});

So we used the Promise constructor to create a new promise and passed in the function with the resolve and reject parameters. We then used the setTimeout function to simulate an asynchronous task. After 1 second, we called the resolve function. This will "resolve" the promise.

Consuming/Handling a Promise

If we run this code, nothing will happen because we haven't dealt with the promise yet. To do that, we use the then method. The then method takes in a function that will be called when the promise is resolved. I do want to mention that there is an alternate way to handle promises and that is with something called Async/Await, which we will be learning in a little bit. For now, we will use the then method.

promise.then(function () {
  console.log('Promise consumed');
});

You also don't have to put the promise into a variable. You could just do this:

new Promise(function (resolve, reject) {
  // Do an async task
  setTimeout(function () {
    console.log('Async task complete');
    resolve();
  }, 1000);
}).then(function () {
  console.log('Promise consumed');
});

If we look in the console, we can see that the Async task complete message is logged first and then the Promise consumed message is logged. To show you that this is asynchronous, I will add a global console log to the bottom of the file.

console.log('Global console log');

Now if we run it, we will see the global console log first because the code is not blocked by the promise. The promise is asynchronous and non-blocking.

Returning Data from a Promise

To return data from a promise, we simply pass it in the resolve function. Let's say we want to return a user object from a promise. We can do that like this:

const promise = new Promise(function (resolve, reject) {
  // Do an async task
  setTimeout(function () {
    resolve({ name: 'John', age: 30 });
  }, 1000);
});

The then method takes in a function that has a parameter for the data that is returned from the promise. We can call it whatever we want, but I will call it user.

promise.then(function (user) {
  console.log(user);

Handling Errors

Remember, we also have a reject function that we can call when the promise is not successful, meaning there is some kind of error.

Let's create a variable that represents an error and then check for it and call the reject function if it exists. We will also pass in an error message.

const promise = new Promise(function (resolve, reject) {
  // Do an async task
  setTimeout(function () {
    let error = false;

    if (!error) {
      resolve({ name: 'John', age: 30 });
    } else {
      reject('Error: Something went wrong');
    }
  }, 1000);
});

If error is set to false, we get the same result as before. But let's set it to true and see what happens.

let error = true;

So, we do see our error message, but notice it also says Uncaught (in promise). This is because we are not handling the error. We can handle the error by using the catch method. The catch method takes in a function that has a parameter for the error message. We can call it whatever we want, but I will call it error.

promise
  .then(function (user) {
    console.log(user);
  })
  .catch(function (error) {
    console.log(error);
  });

Now, we are handling the error and we can see the error message in the console.

We can shorten this with arrow functions and implicit returns.

promise.then((user) => console.log(user)).catch((error) => console.log(error));

finally Method

The finally method is used to execute code after the promise is resolved or rejected. It will run no matter what. I personally have not had too many instances where I needed to add a finally block, but you should know it exists. Let's add a finally method to our promise.

promise
  .then((user) => {
    console.log(user);
    return user.name;
  })
  .then((name) => console.log(name))
  .catch((error) => console.log(error))
  .finally(() => console.log('The promise has been resolved or rejected'));

Now that you know the basic syntax of a promise, in the next lesson, we will re-factor our posts code from callback to promise.