We're going to see how to fix our current problems with async code and callbacks by using promises.

Promises using an analogy

What are promises? Why were they created? Promises were added to the language in ES2015 to make dealing with async code easier and there's a very clear way to understand exactly how they work and what benefits they bring to the table:

So let's take a break from all this coding for a while with some food. Lunch is on me. And let's say we're out getting lunch at a local restaurant, which has a lot of great sandwiches and soups. For this restaurant, they prepare our food and let us know when it's ready to pick up, but we have to wait for it to be ready and then take it to our table.

So you order your food first and are waiting for it to be announced. You are being delivered food in the old manner, where you order, wait by the kitchen and then pick up the food when your name is called. With this approach, you're giving complete control to the kitchen. You stand close by the kitchen, and you're waiting, waiting. And then finally, they give your food and, whoops, it's the wrong thing. Plus you were waiting so intently that you missed an important call in the process.

Meanwhile, the restaurant is trying a different approach with me. Instead of making their customers wait by the kitchen, they give me a little buzzer. You might have seen this at restaurants before show image. This buzzer will tell me the status of my order at any moment, whether it is still pending (meaning, still being made), fulfilled (that my order is ready) or rejected (if they couldn't complete by order for some reason). So since I can clearly see the status of my order, I can do what I want. At first the status of the order is pending, and while that's the case, I pick out the best seat in the restaurant, make a quick call, and then it buzzes, telling me that the order was successful.

This buzzer is the exact same as a promise. Promises tell us the status of async code that we are executing so we can go off and do what we need as its being resolved, and once it comes back as either fulfilled or rejected, we handle either outcome. Unlike the callback based pattern, promises are powerful because they give us control.

How Promises are created

Let's see how promises work, hands-on, by creating one. We create promises with the promise constructor function:

new Promise();

Promises still use callbacks

And what may seem strange about Promises is that to this constructor function, we have to provide a callback function to it, otherwise we'll get an error.

What? Another callback function? Yes, fundamentally, normal promises still implement callbacks. But it is a callback that gives us control over how our code is resolved, through two arguments. The first is called resolve and the second, reject:

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

Once again, we can provide whatever kind of function we want to the callback. I'll use an arrow function for brevity.

The Promise states

So in our restaurant analogy, the buzzer had three states, pending, fulfilled, and rejected. These correspond exactly to all the possible states of a promise.

But how does the promise change from one of these states to another?

Well by default, whenever a promise is created it has a value of pending. And we wait for it to be resolved. With a promise that we create ourselves, we must manually resolve it depending on whether we consider it the operation to have been successful or not.

So that's where the two arguments come into play, resolve and reject, and they are both functions themselves.

resolve is a function that allows you to change the status of the promise to fulfilled.

reject is a function that allows you to change the status of the promise to rejected

Let's take a very simple example, where we use a setTimeout, which will wait for a second and then resolve our promise with the value 'done', just like our console.log like before:

new Promise((resolve, reject) => {
  setTimeout(() => resolve("done"), 1000);
});

So with this code, when we run it, we know what is going to happen. Since we are using a constructor function, we are going to get an instance of a promise returned to us.

Let's see what happens:

// Promise {<pending>}

So we in fact see an instance of a promise was created and on it, we have the initial status, which we know is pending. However, we don't see that the promise was resolved or the value that we passed to the resolve function.

So we can create our promise with the constructor, but how do we listen for changes, for when the promise is fulfilled or is rejected?

So as we know, constructor functions create objects with both properties and methods, and all created promises can invoke two methods, then, and catch, both of which accept a function.

When resolve is called, the code will execute the function passed to the .then method. And if reject is called, the code will execute the function passed to the .catch method.

Only one resolve / reject

And note that for every promise, only one resolve or one reject function can be executed. We can't have a promise call resolve twice or call both resolve and reject. A promise is either fulfilled or rejected.

So let's take our promise instance and put it in a variable of the same name:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => resolve("success"), 1000);
});

And now we can use the instance methods, then and catch to get handle the success or failure case. And what is convenient about it is that we can chain one after the other. We start with then and add on catch:

promise.then().catch();

Both methods accept a callback function as well, and for brevity, the convention is always to use arrow functions:

promise.then(() => console.log("success")).catch(() => console.log("failure"));

And when we execute this, after a second, our promise is fulfilled, since we used the resolve function, which then calls our then callback, which logs 'success', and the catch callback is not run.

That's nice, but we're not getting the value that we passed to the resolve function. How do we get that? From the parameters of the then callback, and we can name it whatever we like:

promise
  .then((value) => console.log(value)) // 'done'
  .catch(() => console.log("failure"));

Now let's try rejecting our promise with the reject function:

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

promise.then((value) => console.log(value)).catch(() => console.log("failure")); // 'failure'

As we would expect, the catch callback runs and we see that it failed. If a promise fails, we don't want to just log that it failed, however, we want to throw an error, which we can do within reject as pass it a message. And we can better indicate the error by using console.error instead of console.log. And then we receive the passed error through catch's callback:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => reject(Error("Promise failed.")), 1000);
});

promise
  .then((value) => console.log(value))
  .catch((error) => console.error(error)); // 'Error: Promise failed.'

finally

And though there browser support for it isn't perfect, there is a new promise method that we have access to call finally, which enables us to run code when the promise has resolved, regardless of whether successfully or unsuccessfully.

Say we want to know when our promise comes back. We'll chain on finally at the end and just log 'done':

const promise = new Promise((resolve, reject) => {
  setTimeout(() => reject(Error('Promise failed.')), 1000);
});

promise
  .then(value => console.log(value))
  .catch(error => console.error(error)); // Error: Promise failed
  .finally(() => console.log('done')) // 'done'

We see our error and then 'done'. So that's a reminder that with finally, either then or catch will still be run.

So now that we are familiar with promises, their benefits over callbacks, their different states, how to construct them, and how to resolve them, let's revisit our callback based example and see how to use promises to make it better.

We'll comment out our original code and start from scratch:

// navigator.geolocation.getCurrentPosition(position => {
//   console.log(position);
// });
// console.log("done");

Let's start by making a Promise constructor with it's callback, that has our resolve and reject functions. The functions always need to be in that order, resolve, then reject:

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

Then we will take our getCurrentPosition callback and put it inside:

new Promise((resolve, reject) => {
  navigator.geolocation.getCurrentPosition((position) => {
    console.log(position);
  });
});

We know that when we get the position from the callback, our promise has resolved successfully, so we pass that position data to resolve:

new Promise((resolve, reject) => {
  navigator.geolocation.getCurrentPosition((position) => {
    resolve(position);
  });
});

What about if it fails? Well getCurrentPosition has another callback for any errors, right after the first callback, where we get access to the error data:

new Promise((resolve, reject) => {
  navigator.geolocation.getCurrentPosition(
    (position) => {
      resolve(position);
    },
    (error) => {
      reject(error);
    }
  );
});

It's as simple as that. Then we get our promise instance from the constructor and put it in a promise variable again:

const promise = new Promise((resolve, reject) => {
  navigator.geolocation.getCurrentPosition(
    (position) => {
      resolve(position);
    },
    (error) => {
      reject(error);
    }
  );
});

So with this reference, we can use promise chaining with then and catch to resolve the fulfilled or rejected promise:

promise.then().catch();

And since we're passing data to both resolve and reject, we can get their values in the then and catch callbacks respectively:

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

And last, we can make use of the finally method to implement our original 'done' console log that was supposed to run after everything was finished:

promise
  .then((position) => console.log(position))
  .catch((error) => console.error(error))
  .finally(() => console.log("done"));

// Position {coords: Coordinates, timestamp: 1572741609253}
// done

And now, we get the result we wanted with the position first and 'done' afterwards, all manage and controlled with promises.

Also note that as an alternative syntax if you want to make this code every more succint, we can just pass resolve and reject as references to getCurrentPosition, instead of having to write out the whole arrow function and they will still be passed the position and error values:

const promise = new Promise((resolve, reject) => {
  navigator.geolocation.getCurrentPosition(resolve, reject);
});

Now, using the power of promises, we can more easily and logically write async code or promisify confusing callback based code. And we'll see how to do a lot more great things with promises in the near future.

As a challenge to you, try to write a function called pause that works like setTimeout but is made as a promise