DEV Community

Cover image for JavaScript for Making API Calls
Olumide
Olumide

Posted on • Updated on

JavaScript for Making API Calls

There are a variety of ways to make an API request with JavaScript, ranging from plain JavaScript to jQuery to additional tools that greatly simplify the process. In this article, we'll utilize a standard JavaScript technique. We'll change our code in following courses to make our API request in a variety of ways. We'll also learn about several tools for working with asynchronous programming in the process. APIs, after all, are asynchronous. While we'll just be using async tools to make API calls in this part, the async tools we'll learn may be used to other asynchronous JavaScript tasks as well.

We'll make an API request the old-fashioned manner in this session, using only vanilla JavaScript. This old-fashioned method is used by all of the tools that jQuery utilizes to perform API requests. However, we won't cover the jQuery technique in this section because the Fetch API is a far superior option. Fetch is likewise based on this time-honored method. So, while you may not utilize this strategy for the independent project in this area (though you certainly may! ), you will have a better knowledge of how technologies like Fetch function when we use them later in this section.

Starting Off

We won't include all of the code for setting up our environment in the next sections. The sample code below is available in a fully functional webpack environment in the repository at the conclusion of the lecture. If you're going to create this project from scratch, you'll need to include a webpack environment, which you can either build yourself or get from the repository at the conclusion of the class. We don't require a __tests__ directory because we aren't testing anything. We don't need a js directory right now. In this session, we'll put all of our JS code in index.js, which is the same naming scheme we've been using with webpack projects. We only need to look at two files for the code sample below: index.html and index.js.
HTML code:

<html lang="en-US">
<head>
  <title>Weather</title>
</head>
<body>
  <div class="container">
    <h1>Get Weather Conditions From Anywhere!</h1>
    <label for="location">Enter a location:</label>
    <input id="location" type="text">
    <button class="btn-success" id="weatherLocation">Get Current Temperature and Humidity</button>
    <div class="showErrors"></div>
    <div class="showHumidity"></div>
    <div class="showTemp"></div>
  </div>
</body>
</html>

Enter fullscreen mode Exit fullscreen mode

For a location, we have a basic form input. There are also various divs for displaying errors, temperature, and humidity.

Let's have a look at the API call's code:

import $ from 'jquery';
import 'bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import './css/styles.css';

$(document).ready(function() {
  $('#weatherLocation').click(function() {
    const city = $('#location').val();
    $('#location').val("");

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

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

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

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

Enter fullscreen mode Exit fullscreen mode

To begin, we'll look at our import declarations. We have a click handler that pulls a city value from a form, puts it in a variable named city, and then erases the form field $('#location') . val(""); This section is just for review.

The following is the first line of the new code:
let request = new XMLHttpRequest();
We create a new XMLHttpRequest (or XHR for short) object and save it in the request variable. XMLHttpRequest is a little deceptive name. These objects are used to interface with servers, which is exactly what API calls are for. They aren't only for XML queries. As previously stated, XML is a rather widespread data format used by APIs. However, JSON is becoming increasingly popular, and XMLHttpRequest objects may be used with JSON as well as other forms of data, not simply XML.

The URL for our API call is then saved in a variable:

 const url = http://api.openweathermap.org/data/2.5/weather?q=${city}&appid=[Add-Your-API-Key]; 
Enter fullscreen mode Exit fullscreen mode

This isn't required, but it does make our code simpler to understand. For the code to operate properly, you'll need to add your own API key in [YOUR-API-KEY-HERE]. Because our string is a template literal with an embedded expression ($city), the value that the user enters in the form is transmitted straight into our URL string via our city variable.

The remainder of the code is divided into three sections:

  • A function that monitors any changes to the XMLHttpRequest's readyState.
  • The request is really processed and sent.
  • A callback function that will be used to display results in the browser. Let's start with the function that monitors the XMLHttpRequest for changes:
request.onreadystatechange = function() {
  if (this.readyState === 4 && this.status === 200) {
    const response = JSON.parse(this.responseText);
    getElements(response);
  }
};

Enter fullscreen mode Exit fullscreen mode

Onreadystatechange is a property of our XMLHttpRequest object. This attribute can be set to the value of a function that performs anything we desire. We have an anonymous function (an unnamed function) set to the value of that property in the example above.

We could even tweak the code to just track changes in the ready state:

request.onreadystatechange = function() {
  console.log(this.readyState);
};
Enter fullscreen mode Exit fullscreen mode

If we did, the console would show the following. The comment has been included.

1 // Opened
2 // Headers Received
3 // Loading
4 // Done
Enter fullscreen mode Exit fullscreen mode

These numbers represent the many states in which our XMLHttpRequest object may be found. (Because this is the initial state - and the readyState hasn't changed yet - you wouldn't see 0, which corresponds to Unsent.)

Note that if you attempt this in the console, ESLint will complain about no-unused-vars. This is due to the fact that the getElements() method, which we define later in the code, is no longer in use. To make ESLint feel better, temporarily comment it out. Also, when you're finished, be sure to restore the code to its original state.
We wouldn't want to do anything until this.readyState is 4 because the data transmission isn't finished yet. At work, this is classic async. Once this is done and this if this.readyState === 4. We'll do anything with the data if this.status === 200. Why does this happen? Is it necessary for this.status === 200 to be included in our conditional? We discussed how a 200 response signals a successful API request in the last lecture. To put it another way, before our code analyses the data, the API request must be successful and the data transfer must be complete.

When the conditional is true, we execute the following code:

const response = JSON.parse(this.responseText);
Enter fullscreen mode Exit fullscreen mode

This.responseText is another built-in property of XMLHttpRequest objects, as you might expect. Once a server answer is received, it is immediately filled. It should be evident by now that XMLHttpRequest objects are quite strong and perform a significant amount of work for us.
The built-in JSON.parse method in JavaScript is used to parse this.responseText. This guarantees that the data is formatted correctly as JSON data. Otherwise, our code won't identify the data as JSON, and when we try to obtain data from it using dot notation, we'll receive an error. Working with APIs necessitates the use of the JSON.parse() method. Other programming languages, as we mentioned in a previous lecture, include methods for parsing JSON as well.

Then, using the data in the response variable, we'll build a callback:

getElements(response);
Enter fullscreen mode Exit fullscreen mode

A callback occurs when a function calls another function. In a moment, we'll go through this in further detail.

Before we do that, let's talk about XMLHttpRequest objects in more depth. By placing a breakpoint within our conditional and then executing the code in the browser, we can see exactly what characteristics an XMLHttpRequest object has.

request.onreadystatechange = function() {
  if (this.readyState === 4 && this.status === 200) {
    debugger;
    ...
  }
};
Enter fullscreen mode Exit fullscreen mode

It's wiser to add a breakpoint from the Sources tab - the sample above only demonstrates where the breakpoint should be placed.
An XMLHttpRequest object, as you can see, has a lot of capabilities. Most of these assets aren't worth worrying about right now. There are, however, a few that will come in use throughout this section:

responseText: We've previously talked about this. It contains the response's text. (The identical text can also be found in the response property.)
Status: The status code is the API status code. A score of 200 indicates that it was a success. There are a variety of different codes, such as 404 not found.
statusText: As you can see, it's "OK." With a status code of 200, this is standard. That indicates we're ready to go! If anything goes wrong, though, we could get a more descriptive error message like "not found" or "not permitted."

Let's get back to our new code:

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

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

// We've covered everything except for the two lines below!
request.open("GET", url, true);
request.send();
Enter fullscreen mode Exit fullscreen mode

Except for the last two lines (which are highlighted in the comment), we've covered everything.

We've created a new XMLHttpRequest object and set a method to the onreadystatechange property to listen for changes in the object's ready state at this point in our code, but we haven't done anything with it yet. The request must still be opened and sent.

 request.open("GET", url, true);
 request.send();
Enter fullscreen mode Exit fullscreen mode

The method of the request (in this instance GET), the url (which we saved in a variable named url), and a boolean indicating whether the request should be async or not are all sent to XMLHttpRequest.open(). We want the request to be async once more; we don't want our users' browsers to freeze! The three parameters will nearly always be the same for the API calls we make in this section; the only exception will be if you make a "POST" or other form of request instead of "GET."
We send the request after we've opened it. The readyState of the XMLHttpRequest object will change, as we've already explained, and the function we've attached to the object's onreadystatechange will fire each time the readyState changes. Finally, our getElements() method will be run when our conditional in the function we've linked to the onreadystatechange property is activated.
A callback occurs when a function calls another function. Callbacks may rapidly become perplexing, particularly when one function calls another, which in turn calls another, and so on. As a result, they may be somewhat daunting to newcomers. Remember that a callback is simply a function calling another function when you see scary-looking callbacks in the real world. In a later lesson, when we cover the notion of "callback hell," we'll describe why callbacks may be so frightening.
For the time being, it's crucial to understand that callbacks are one method JavaScript writers may deal with async code. It used to be the sole option for dealing with async code. Fortunately, we now have access to new technologies that will make our life simpler. Later in this section, we'll look at some of these tools.

Because we need to wait till our conditional is triggered before using getElements, we need to utilize a callback here (). Keep in mind that JavaScript is a non-blocking language. Even if some of the code is async, it will continue to run.

Let's see what would happen if we didn't utilize a callback.

// Note: This code will not work! It's meant to show why we need to structure our code to use a callback.

    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);
Enter fullscreen mode Exit fullscreen mode

When we execute request.send() in the code above, our request is submitted to the server. Keep in mind that this will take some time. Our request will be accepted (or denied) by the server, and we will receive a response. We must first wait for the answer to load before parsing it. JavaScript, on the other hand, is not a blocking language. That implies it won't wait for request.send() to finish before continuing. The call to getElements(response) will happen right away, and we'll get the following error:

Cannot read property 'main' of undefined
Enter fullscreen mode Exit fullscreen mode

This is a typical async problem getElements(response) is not async, although request.send() is. When getElements() is invoked, the result will still be undefined since the function is still running. The answer will be specified later, but our code will break before that.

This is why a callback is required. Let's look at our original code once more:

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

...

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

getElements(response) will not be invoked in this code until the conditional is true. In other words, we ensure that the function doesn't start until we receive a response from the server by utilizing a callback.

One of the many essential use cases for callbacks is async code. Callbacks can assist us in determining the order in which functions should be executed. If we require a sync function to execute after an async function, we may use a callback to ensure that the code runs in the expected sequence.

Of course, when we need a sequence of sync and async methods to run in a specified order, things may quickly get strange.

Conclusion

We covered how to build and send an XMLHttpRequest object in this lecture. You should have a better knowledge of how JavaScript creates HTTP requests after doing so. We also spoke about how to utilize callbacks to ensure that our code runs in the order we want it to.

Discussion (2)

Collapse
lukeshiru profile image
Luke Shiru

In 2022 you don't need jQuery or XMLHttpRequest to communicate with an API, you can just use Vanilla JavaScript with fetch. You don't even need jQuery for Bootstrap. Here's your code without that:

import "bootstrap";
import "bootstrap/dist/css/bootstrap.min.css";
import "./css/styles.css";

const ENDPOINT = "https://api.openweathermap.org/data/2.5/weather";
const API_KEY = "YOUR API KEY HERE";

const locationInput = document.querySelector("#location");
const humidityOutput = document.querySelector(".showHumidity");
const temperatureOutput = document.querySelector(".showTemp");

document.querySelector("#weatherLocation")?.addEventListener("click", () => {
    const { value: city } = locationInput;
    locationInput.disabled = true;

    fetch(`${ENDPOINT}?${new URLSearchParams({ q: city, appid: API_KEY })}`)
        .then(response => response.json())
        .then(({ main: { humidity, temp } }) => {
            locationInput.value = "";
            humidityOutput.textContent = `The humidity in ${city} is ${humidity}%`;
            temperatureOutput.textContent = `The temperature in ${city} is ${temp} degrees.`;
        })
        .finally(() => (locationInput.disabled = false));
});
Enter fullscreen mode Exit fullscreen mode

More info here, here and here.

Cheers!

Collapse
shittu_olumide_ profile image
Olumide Author

Nice, Thanks.