Learn about the different methods for when making HTTP Requests
Contents
- Introduction to Requests
- AJAX
- XML
- JSON
- XHR Requests
- Fetch Requests
- Axios Library
- Async/Await with Axios Requests
- Practice with JSON Placeholder API
Introduction to Requests
Requests are made from the client side in order retrieve data from servers and APIs. There are multiple ways to make requests but it's important to note that requests take time, which is why it's necessary to understand AJAX.
AJAX stands for Asynchronous JavaScript and XML, but in modern day we actually use JSON instead of the XML format. Both of these formats represent how information is sent in the browser.
XML stands for Extensible Markup Language. The format looks something like this:
<name>
<first>John</first>
<last>Doe</last>
</name>
<email>John@gmail.com</email>
JSON stands for JavaScript Object Notation. The format looks something like this:
{
"firstName":"John",
"lastName":"Doe",
"email": "John@gmail.com"
}
XHR Requests
XHR stands for XML Http Requests. This was the original way of sending requests through JavaScript, but this doesn't support the use of promises, so it can become callback hell!
To get started you have to create a new object using the new XMLHttpRequest() function. Then we have to create one callback that processes what happens when we successfully retrieve data and one request to process errors. These methods are onload() and onerror().
The open() method can be used to process the requests. The first parameter would be the type of request, and what url to send the request to.
The setRequestHeader() method changes the header of the request being sent.
The send() method sends the request once the code reaches this line of code.
const firstReq = new XMLHttpRequest();
firstReq.addEventListener('load', function() { //Alternative to .onload
console.log('IT WORKED!');
const data = JSON.parse(this.responseText); //Gets the JSON data
for (let planet of data.results) {
console.log(planet.name); //Returns list of planet names
}
});
firstReq.addEventListener('error', () => { //Alternative to .onerror
console.log('ERROR!');
});
firstReq.open('GET', 'https://swapi.co/api/planets/');
firstReq.send();
console.log('Request Sent!');
Chaining XHR Requests is when the code can get extremely messy. For example:
const firstReq = new XMLHttpRequest();
firstReq.addEventListener('load', function() {
console.log('FIRST REQUEST WORKED!');
const data = JSON.parse(this.responseText);
const filmURL = data.results[0].films[0];
const filmReq = new XMLHttpRequest(); //Another request is being made
filmReq.addEventListener('load', function() {
console.log('SECOND REQUEST WORKED!');
const filmData = JSON.parse(this.responseText);
console.log(filmData.title);
});
filmReq.addEventListener('error', function(e) {
console.log('ERROR!', e);
});
filmReq.open('GET', filmURL);
filmReq.send();
});
firstReq.addEventListener('error', (e) => {
console.log('ERROR!');
});
firstReq.open('GET', 'https://swapi.co/api/planets/');
firstReq.send();
console.log('Request Sent!');
Fetch Requests
After looking at XHRs, you can see how the code can get messy really fast as you chain more requests. The Fetch API makes this code a lot cleaner. The major advantage is that it supports promises.
To get started, pass in a url to fetch() and this will return a promise that will either be resolved or rejected.
The throw keyword will pass along the error to the .catch() block when there is an error with the response.
fetch('https://swapi.co/api/planetsuy21/')
.then((response) => {
if (!response.ok)
throw new Error(`Status Code Error: ${response.status}`);
response.json().then((data) => { //Returns this on success
for (let planet of data.results) {
console.log(planet.name);
}
});
})
.catch((err) => {
console.log('SOMETHING WENT WRONG WITH FETCH!');
console.log(err);
});
"Note that fetch won't reject on HTTP error status even if the response is an HTTP 404 or 500. If there is any response, it will go into the .then() block so you have to catch the error on your own. The .catch() block is for rejections on network failures or if something prevented the request from completing like internet not working."
When chaining fetch requests, there isn't an issue with nesting. The structure becomes flattened, which is another great benefit of fetch.
fetch('https://swapi.co/api/planets/')
.then((response) => {
if (!response.ok)
throw new Error(`Status Code Error: ${response.status}`);
return response.json();
})
.then((data) => {
console.log('FETCHED ALL PLANETS (first 10)');
const filmURL = data.results[0].films[0];
return fetch(filmURL);
})
.then((response) => {
if (!response.ok)
throw new Error(`Status Code Error: ${response.status}`);
return response.json();
})
.then((data) => {
console.log('FETCHED FIRST FILM, based off of first planet');
console.log(data.title);
})
.catch((err) => {
console.log('SOMETHING WENT WRONG WITH FETCH!');
console.log(err);
});
Let's refactor this since there's a lot of repeating functions.
const checkStatusAndParse = (response) => {
if (!response.ok) throw new Error(`Status Code Error: ${response.status}`);
return response.json();
};
const printPlanets = (data) => {
console.log('Loaded 10 more planets...');
for (let planet of data.results) {
console.log(planet.name);
}
return Promise.resolve(data.next);
};
const fetchNextPlanets = (url = 'https://swapi.co/api/planets/') => {
return fetch(url);
};
fetchNextPlanets()
.then(checkStatusAndParse)
.then(printPlanets)
.then(fetchNextPlanets)
.then(checkStatusAndParse)
.then(printPlanets)
.then(fetchNextPlanets)
.then(checkStatusAndParse)
.then(printPlanets)
.catch((err) => {
console.log('SOMETHING WENT WRONG WITH FETCH!');
console.log(err);
});
Axios Library
Axios is a popular library that simplifies fetch requests. What makes this great is that you can use it on both the client and server side.
To get started, download axios on the terminal or use it's cdn.
One main advantage of using axios is that the data is already parsed for us. This eliminates one of the redundancies of fetch where we have to manually parse and throw errors to our catch block.
axios
.get('https://swapi.co/api/planets/')
.then((res) => {
//We don't have to parse the JSON!
console.log(res.data);
})
.catch((err) => {
console.log('IN CATCH CALLBACK!');
console.log(err);
});
axios
.get('https://swapi.co/api/planetaslkjdaklsjds/') //BAD URL!
.then((res) => {
//We don't need to check for a 200 status code, because...
//Axios will reject the promise for us, unlike fetch!
console.log(res.data);
})
.catch((err) => {
//In this example with a not-found URL, this callback will run...
console.log('IN CATCH CALLBACK!');
console.log(err);
});
Chaining is very similar to fetch, but the parsing and throwing error step is eliminated.
const fetchNextPlanets = (url = 'https://swapi.co/api/planets/') => {
return axios.get(url);
};
const printPlanets = ({ data }) => {
console.log(data);
for (let planet of data.results) {
console.log(planet.name);
}
return Promise.resolve(data.next);
};
fetchNextPlanets()
.then(printPlanets)
.then(fetchNextPlanets)
.then(printPlanets)
.then(fetchNextPlanets)
.then(printPlanets)
.catch((err) => {
console.log('ERROR!', err);
});
Async/Await with Requests
The async keyword is used before a function declaration and this allows the function to return a promise.
If the function returns a value, the promise will be resolved with that value and if the function throws an exception, the promise will be rejected.
// REGULAR function returns a string:
// function greet() {
// return 'HELLO!!!';
// }
// THE ASYNC KEYWORD!
// This function now returns a promise!
async function greet() {
return 'HELLO!!!'; //resolved with the value of 'HELLO!!!'
}
greet().then((val) => {
console.log('PROMISE RESOLVED WITH: ', val);
});
async function add(x, y) {
if (typeof x !== 'number' || typeof y !== 'number') {
throw 'X and Y must be numbers!';
}
return x + y;
}
// Equivalent non-async function...
// function add(x, y) {
// return new Promise((resolve, reject) => {
// if (typeof x !== 'number' || typeof y !== 'number') {
// reject('X and Y must be numbers!');
// }
// resolve(x + y);
// });
// }
add(6, 7)
.then((val) => {
console.log('PROMISE RESOLVED WITH: ', val);
})
.catch((err) => {
console.log('PROMISE REJECTED WITH: ', err);
});
The await keyword can only be used inside functions declared with async.
Await will pause the execution of a function and wait for a promise to be resolved.
async function getPlanets() {
const res = await axios.get('https://swapi.co/api/planets/');
console.log(res.data); //only runs once the previous line is complete (the axios promise is resolved)
}
getPlanets();
// Without async/await...
// function getPlanets() {
// return axios.get('https://swapi.co/api/planets/');
// }
// getPlanets().then((res) => {
// console.log(res.data);
// });
We can also use Promise.all() to handle requests in parallel with one another.
// PARALLEL REQUESTS!
async function getAllPokemon() {
const prom1 = axios.get('https://pokeapi.co/api/v2/pokemon/1');
const prom2 = axios.get('https://pokeapi.co/api/v2/pokemon/2');
const prom3 = axios.get('https://pokeapi.co/api/v2/pokemon/3');
const results = await Promise.all([ prom1, prom2, prom3 ]);
printPokemon(results);
}
function printPokemon(results) {
for (let pokemon of results) {
console.log(pokemon.data.name);
}
}
getAllPokemon();
Comments