Lesson Monday

In the last lesson, we learned how to make API calls with jQuery’s ajax() function. In this lesson, we’ll learn how to make an API call with vanilla JavaScript using JS’s built-in XMLHttpRequest object. While you’ll probably use jQuery for making API calls on this week’s code review, it’s important to know about XMLHttpRequest objects.

jQuery’s ajax() functionality uses an XMLHttpRequest object to communicate with the server. In other words, ajax() is a wrapper around this core functionality in JavaScript. A wrapper is often a convenience function “wrapped around” another function (or functions). Learning about XMLHttpRequest objects will give you a better understanding of what ajax() is doing under the hood.

We’ll start by taking a look at the code and then going over it. Once again, we only need two files in our root directory: a weather.html file and a weather-interface.js file. We’ll use the same HTML from the last lesson; the only difference in our interface file is how we make the API call.

weather-interface.js
$(document).ready(function() {
  $('#weatherLocation').click(function() {
    let city = $('#location').val();
    $('#location').val("");

    let request = new XMLHttpRequest();
    let url = `http://api.openweathermap.org/data/2.5/weather?q=${city}&appid=[API-KEY-GOES-HERE]`;

    request.onreadystatechange = function() {
      if (this.readyState === 4 && this.status === 200) {
        let response = JSON.parse(this.responseText);
        getElements(response);
      }
    }

    request.open("GET", url, true);
    request.send();

    getElements = function(response) {
      $('.showHumidity').text(`The humidity in ${city} is ${response.main.humidity}%`);
      $('.showTemp').text(`The temperature in Kelvins is ${response.main.temp} degrees.`);
    }
  });
});

We start by instantiating a new XMLHttpRequest object and storing it in a variable called request. We also store the URL in a url variable to make our code a little easier to read.

The rest of the code consists of three parts:

  • a function that listens for any changes to the readyState of the XMLHttpRequest;
  • the actual processing and sending of the request;
  • and a function that will be used as a callback to display results in the browser.

Let’s take a look at each chunk of code one at a time:

request.onreadystatechange = function() {
      if (this.readyState === 4 && this.status === 200) {
        let response = JSON.parse(this.responseText);
        getElements(response);
      }
    }

Before we send our request, we create a listener. Each time the readyState of our XMLHttpRequest changes, this function will be called. There are five ready states, starting with 0, which means the request hasn’t been sent yet, and ending with 4, which means the request is done. We want to wait until the request is finished; we also want to ensure that the API call was completed successfully, which is why the status needs to be 200 OK.

At that point, we can parse the responseText with JavaScript’s built-in JSON.parse method. This is an absolutely essential method to know because JSON is a very common format. The responseText is a built-in property of the XMLHttpRequest. Once we’ve parsed the JSON, we’ll make a callback. We’ll get back to that in a moment.

Next, we open and send our request:

 request.open("GET", url, true);
 request.send();

open() takes three arguments: the method of the request (in this case GET), the url (which we stored in a variable), and a boolean for whether the request should be async or not. Once again, we want the request to be async; we don’t want the browser to freeze up for our users!

Once we’ve opened the request, we send it.

Finally, we have our callback. But why do we even need a callback? Why can’t we just do something like this?

    let response;

    request.onreadystatechange = function() {
      if (this.readyState === 4 && this.status === 200) {
        response = JSON.parse(this.responseText);
      }
    }

    request.open("GET", url, true);
    request.send();
    getElements(response);

Let’s go over the difference in this code and why it won’t work. Instead of using getElements() as a callback, we’ll call it directly after request.send(). (The getElements() function isn’t included in the snippet above for simplicity’s sake.)

We declare a response variable. We open and send our request. Then, when the request is ready, our response variable will be updated to store the parsed JSON text. However, when we call getElements(response), we get the following error:

Cannot read property 'main' of undefined

This is a classic async issue. request.send() is async but getElements(response) is not. The code will continue to run, so the response will still be undefined when getElements() is called. The response will eventually be defined, but our code will break first.

This is why we need a callback. Let’s take a look at our original code again:

request.onreadystatechange = function() {
      if (this.readyState === 4 && this.status === 200) {
        let response = JSON.parse(this.responseText);
        getElements(response);
      }
    }

...

    getElements = function(response) {
      $('.showHumidity').text(`The humidity in ${city} is ${response.main.humidity}%`);
      $('.showTemp').text(`The temperature in Kelvins is ${response.main.temp} degrees.`);
    }

In this code, getElements(response) won’t be called until the conditional becomes true. In other words, by using a callback, we ensure the function doesn’t run until after we get a response from the server.

This is one of the most important use cases for callbacks. They allow us to determine the order that functions should run. If we need a sync function to run immediately after an async function, then we can use a callback to make sure the code runs in the order we expect.

In this lesson, we learned how to create and send a XMLHttpRequest object. Doing so should give you a better understanding of how JavaScript makes HTTP requests. In the process, we’ve also learned more about how ajax() works, since it uses a XMLHttpRequest object under the hood. Just as importantly, we discussed how we can use callbacks to make sure our code runs in the order we expect.