Lesson Tuesday

Promises are one of the most powerful new features in ES6. A promise allows us to wrap async code and then wait for the result of that code before moving on. In this lesson, we’ll discuss why promises are so useful and how we can use them to tidy up our async code.

Promises have been a key concept in JavaScript development for quite some time, but up until ES6, they weren’t native to JS. Instead, developers relied on promise libraries like Bluebird.js or used jQuery’s then() method. Some developers still prefer to use promise libraries instead of ES6’s native functionality because these libraries have more features or run more quickly than ES6 promises.

We’ve already learned how to use callbacks to ensure our code runs in order. But what happens when we need to chain many async and sync functions together? Here’s an abstracted example of what that might look like using pseudocode:

doAsync(function() {
  doAsync2(function() {
    doAsync3(function() {
      doAsync4(function() {
        doAsync5(function() {
          // return something here.
        });
      });
    });
  });
});

Here we have callback after callback, each nested inside the previous one. This is known as the pyramid of doom because of the pyramid-like shape of the indented code. It’s also known as callback hell. It’s difficult to read and reason about. It’s also prone to bugs.

Promises were created in order to deal with this issue. With promises, we could write the above code more like this (once again, we’re using pseudocode):

doAsync()
  .then doAsync()
  .then doAsync2()
  .then doAsync3()
  .then doAsync4()
  .then doAsync5()

Promises make it much easier to chain together async functions and read and reason about our code. So how can we incorporate them into our code?

In the pseudocode example above, we use the method .then(), which returns a Promise object. A promise can have three states:

  • Pending: The object's initial state. A pending operation has been started, but not completed yet.
  • Fulfilled: A promise is fulfilled when the operation has been successfully completed.
  • Rejected: A promise is rejected means when the operation fails for some reason.

Imagine you’re waiting to renew your driver’s license at the DMV. When you go in, you get a piece of paper with a number on it. You wait until your number is called and then you go to the counter to renew your license.

That piece of paper is similar to a promise. It represents an appointment you’ll have in the future, but that appointment doesn’t exist yet. While you’re waiting for your number to be called, the promise is pending. The promise will either be fulfilled (driver’s license renewed...yay!) or rejected (better study for the driving test more…)

Once the promise is either fulfilled or rejected, it is complete and becomes immutable. An immutable value can’t be changed. Our promise also can’t be used again, just as you can’t use a number to get back in line at the DMV after your appointment is finished.

With that in mind, let’s use promises to return the result of an API call. We’ll solve the problem in two different ways. In this lesson, we’ll use jQuery’s then() method. In the next lesson, we’ll build a promise from scratch using new ES6 syntax and call JavaScript’s native then() method on that promise. It may seem a bit confusing at first that then() is a method in both jQuery and ES6 (as well as other promise libraries), however, it makes sense that methods that do similar things are often named the same across libraries.

Remember, however, that we can create prototypes on different objects that have the same name but different functionality. With get() and ajax(), jQuery creates a deferred object that can use jQuery’s then() method. With ES6, we’ll create a new Promise from scratch and then take advantage of its then() method. Ultimately, both then() methods actually do very similar things, but it’s important to realize that they are actually different methods being called on different objects.

Once again, we only need a weather.html file and a weather-interface.js file in our root directory. Here’s our updated .js file:

weather-interface.js
$(document).ready(function() {
  $('#weatherLocation').click(function() {
    let city = $('#location').val();
    $('#location').val("");
    $.get(`http://api.openweathermap.org/data/2.5/weather?q=${city}&appid=[API-KEY-GOES-HERE]`).then(function(response) {
      $('.showHumidity').text(`The humidity in ${city} is ${response.main.humidity}%`);
      $('.showTemp').text(`The temperature in Kelvins is ${response.main.temp} degrees.`);
    }).fail(function(error) {
      $('.showErrors').text(`There was an error processing your request: ${error.responseText}. Please try again.`);
    });
  });
});

First, we utilize a new jQuery function: get(). This function is shorthand for ajax(). Since we only need to make a GET request without passing in other options, it makes sense to use this method. jQuery also provides other shorthand functions such as post().

Next, we use jQuery’s then() method to wrap the GET request in a promise. The code inside then() will be called if the promise is fulfilled. If the promise is rejected, it will be passed into fail().

Now that we’re utilizing a promise, we no longer need to rely on callbacks to make our async code run in order. While the benefits of using promises may not be obvious in this code, they quickly become more useful in a large codebase when we need to chain together async code.

In this lesson, we’ve gone over how we can use promises to bring more order and readability to our code. We also used jQuery’s then() method to create and return the results of a promise. In the next lesson, we’ll see how we can use ES6 to create and work with promises.