How to Make AJAX Calls in JavaScript
In this tutorial, we’ll learn how to make AJAX calls in JavaScript.
AJAX
The term AJAX
stands for Asynchronous JavaScript And XML
The term AJAX
is used in JavaScript for making asynchronous network request to fetch resources. Resources are not limited to XML, as the term suggest which is confusing. The term AJAX
is also used to fetch resources as JSON, HTML, or Plain Text. You may have heard that term already.
There are multiple ways to make network request and fetch data from the server. We’ll look at them one by one.
XMLHttpRequest
The XMLHttpRequest
object (also known as XHR in short) is used in older days to retrieve data from the server asynchronously.
The XML
comes in the name because it is first used to retrieve XML data. Now this can also be used to retrieve JSON, HTML or Plain text.
Example: GET
function success() {
var data = JSON.parse(this.responseText);
console.log(data);
}
function error(err) {
console.log('Error Occurred :', err);
}
var xhr = new XMLHttpRequest();
xhr.onload = success;
xhr.onerror = error;
xhr.open("GET", "https://jsonplaceholder.typicode.com/posts/1");
xhr.send();
We see that to make a simple GET request, we need two listeners to handle success and failure of the request. We also need to make calls to open()
and send()
methods. The response from the server is stored in the responseText
variable, which is converted to JavaScript object using JSON.parse()
.
Example: POST
function success() {
var data = JSON.parse(this.responseText);
console.log(data);
}
function error(err) {
console.log('Error Occurred :', err);
}
var xhr = new XMLHttpRequest();
xhr.onload = success;
xhr.onerror = error;
xhr.open("POST", "https://jsonplaceholder.typicode.com/posts");
xhr.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
xhr.send(JSON.stringify({
title: 'foo',
body: 'bar',
userId: 1
})
);
We see that POST request is similar to GET request. We need to additionally set the request header “Content-Type” using setRequestHeader
and send the JSON body as string using JSON.stringify
in the send
method.
XMLHttpRequest vs Fetch
We have used XMLHttpRequest
for several years to request data. The modern fetch()
API allows you to make network requests similar to XMLHttpRequest
(XHR). The main difference is that the fetch()
API uses Promises, which enables a simpler and cleaner API, avoiding callback hell and having to remember the complex API of XMLHttpRequest
.
Fetch API
Fetch is a native JavaScript API to make AJAX calls which is supported by most of the browsers and widely used now a days.
API Usage
fetch(url, options)
.then(response => {
// handle response data
})
.catch(err => {
// handle errors
});
API Arguments
The fetch()
API takes two arguments:
- url is a mandatory argument, which is a path to the resource you want to fetch.
- options is an optional argument. You don’t need to provide this argument to make simple GET request. You need to pass this argument to provide additional information about the request such as
- method: GET | POST | PUT | DELETE | PATCH
- headers: Request Headers Object for e.g. { “Content-type”: “application/json; charset=UTF-8” }
- mode: cors | no-cors | same-origin | navigate
- cache: default | reload | no-cache
- body: Request body generally used in POST request
API returns Promise Object
The fetch()
API returns a promise object.
- The promise is rejected if there is a network error, this is handled in the
.catch()
block. - The promise is resolved if there is a response from the server with any status code such as 200, 404, 500. Response Object can be handled in the
.then()
block.
Error Handling
Please note that we expect status code as 200 (OK status) for successful response but fetch()
API resolved the promise even if response is coming with error status code such as 404 (Resource Not found) and 500 (Internal Server Error). We need to handle those explicitly in .then()
block.
We can see HTTP-status in response
object:
- status – HTTP status code, e.g. 200.
- ok – boolean, true if the HTTP status code is 200-299.
Example: GET
const getTodoItem = fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.catch(err => console.error(err));
getTodoItem.then(response => console.log(response));
Response
➤ { userId: 1, id: 1, title: "delectus aut autem", completed: false }
Two things to note in above code:-
fetch
API returns a promise object which we can assign to a variable and execute later.- You have to additionally call
response.json()
to convert response object to JSON
Error Handling
Let’s see what happens when HTTP GET
request throw 500 error:-
fetch('http://httpstat.us/500') // this API throw 500 error
.then(response => () => {
console.log("Inside first then block");
return response.json();
})
.then(json => console.log("Inside second then block", json))
.catch(err => console.error("Inside catch block:", err));
Inside first then block
➤ ⓧ Inside catch block: SyntaxError: Unexpected token I in JSON at position 4
We see that even though API throw 500 error, It still goes inside first then()
block where it’s not able to parse error JSON and throw error which is caught by catch()
block.
That means we need to handle such errors explicitly like this if we are using fetch()
API:-
fetch('http://httpstat.us/500')
.then(handleErrors)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error("Inside catch block:", err));
function handleErrors(response) {
if (!response.ok) { // throw error based on custom conditions on response
throw Error(response.statusText);
}
return response;
}
➤ Inside catch block: Error: Internal Server Error at handleErrors (Script snippet %239:9)
Example: POST
fetch('https://jsonplaceholder.typicode.com/todos', {
method: 'POST',
body: JSON.stringify({
completed: true,
title: 'new todo item',
userId: 1
}),
headers: {
"Content-type": "application/json; charset=UTF-8"
}
})
.then(response => response.json())
.then(json => console.log(json))
.catch(err => console.log(err))
Response
➤ {completed: true, title: "new todo item", userId: 1, id: 201}
Two things to note in above code:-
POST
request is similar toGET
request. We need to additionally send themethod
,body
andheaders
properties in second argument offetch()
API.- We have to explicitly
JSON.stringify()
the requestbody
params
Axios API
Axios API is very similar to fetch
API with few enhancements. I personally prefer to use Axios
API instead of fetch()
API due to following reasons:-
- Provides different methods for GET
axios.get()
, POSTaxios.post()
, … which makes your code concise - Consider 299++ response codes such as 404, 500 as errors which can be handled in
catch()
block so you do not need to handle those errors explicitly - It provides backward compatibility with old browsers such as IE11
- It returns the response as JSON object so you do not need to do any parsing
- It takes the POST request body as JSON object so you do not need to do any stringify
Example: GET
// way to include script in chrome console
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'https://unpkg.com/axios/dist/axios.min.js';
document.head.appendChild(script);
axios.get('https://jsonplaceholder.typicode.com/todos/1')
.then(response => console.log(response.data))
.catch(err => console.error(err));
Response
{ userId: 1, id: 1, title: "delectus aut autem", completed: false }
We see that we get the response data directly using response.data
without any parsing object unlike fetch()
API.
Error Handling
axios.get('http://httpstat.us/500')
.then(response => console.log(response.data))
.catch(err => console.error("Inside catch block:", err));
➤ Inside catch block: Error: Network Error
We see that 500 error is also get caught by catch()
block unlike fetch()
API where we have to handle them explicitly.
Example: POST
axios.post('https://jsonplaceholder.typicode.com/todos', {
completed: true,
title: 'new todo item',
userId: 1
})
.then(response => console.log(response.data))
.catch(err => console.log(err))
➤ {completed: true, title: "new todo item", userId: 1, id: 201}
We see that POST
method is very short and concise. You can directly pass the request body parameters unlike fetch()
API where we stringify them.