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>
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.`);
}
});
});
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];
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'
sreadyState
. - 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);
}
};
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);
};
If we did, the console would show the following. The comment has been included.
1 // Opened
2 // Headers Received
3 // Loading
4 // Done
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);
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);
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;
...
}
};
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();
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();
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);
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
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.`);
}
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.
Top comments (1)
Nice, Thanks.