AJAX makes our page dynamic and lets us refresh data in our web pages without refreshing the page. It makes user pages interactive and creates a slicker user experience for the user. AJAX stands for Asynchronous JavaScript and XML. It’s used to describe the way that we use the XMLHttpRequest
object to refresh part of the page by getting data from the server via HTTP requests and manipulate the DOM to refresh the data.
The HTML DOM changes the dynamically. AJAX allows us to use the XMLHttpRequest
object to get data from the server and manipulate the DOM without blocking the execution of other parts of the JavaScript program. Despite that AJAX stands for Asynchronous JavaScript and XML, JSON is frequently used for sending and retrieving data from the server. JSON stands for JavaScript Object Notation. JSON is a data format that very close to a JavaScript object, except that it can’t contain any functions or dynamic code. Only string, numbers, arrays and objects without functions are allowed.
Old style web pages refresh the whole page to refresh the data. This is getting less and less common today as it creates a less pleasant experience than just refreshing parts of the page that’s needed to refresh. Refreshing the whole page would make the whole page flash and everything has to be loaded again.
AJAX is also great for updating live data on a page since it only refreshes parts of a page that’s need to refresh. It’s good for tables, charts, emails, and things that refresh periodically.
To view AJAX in action, we can look at the browser’s development console. For example, in Chrome, we can go to a website like weather.com and then we can press F12 or right click the browser window and click Inspect to open the development console. Then we can go to the Network tab. In there, we have the XHR button, XHR stands for XmlHttpRequest, which is what’s originally used for AJAX. On the left side of the XHR section, click on one of the entries. Next click on Preview, and then we can see something like the following:
This is the JSON data that’s parsed by the browser and can be inserted to the DOM of a web page. All AJAX is sending data via HTTP requests and then response will be obtained from the the server, then the data is populated on the page via manipulating the DOM. Most modern browsers work the same way and support AJAX.
Same-Origin Policy
In most cases, requests are made to a server that has a different domain from the server that has a different domain from the originating server. If we do that without setting some options, we will get an error since most browsers by default stop cross origin requests from being made without setting specific options on the server and the client. The purpose of this is to prevent browsers from downloading code that’s unknown to the browser. This policy applies everywhere, even on our local computer, since it’s can be used to compromise the security of our computers no matter where the data is downloaded from.
To allow requests to request between computers with different domains from a web browser. The server has to enable Cross-Origin Resource Sharing, or CORS for short. This has to be enabled on the server side. If we look at a cross origin HTTP request, we should see an OPTIONS request before the actual request is made and in we should see the following in the response headers:
Access-Control-Allow-Origin: \*
Alternatively, we can put the client and server side code in the same domain. This wouldn’t violate the same origin policy in browsers since the request from the browser is made from the same domain from the server.
Using the XMLHttpRequest Object
We can use the XMLHttpRequest
object to make a request to a server and retrieve its response. This can be done with a simple example. In this example, we will load content from a text file and then put the content inside a page. Create a file called index.html
and add:
<html>
<head>
<title>XMLHttpRequest</title>
</head>
<body>
<h1>XML HTTP Request</h1>
<button id="load">Load Data</button>
<div id="content"></div>
<script src="script.js"></script>
</body>
</html>
The create a file called script.js
and add:
const reqListener = response => {
const content = document.getElementById("content");
content.innerHTML = response.currentTarget.response;
};
const loadData = () => {
const req = new XMLHttpRequest();
req.onload = reqListener;
req.open("get", "file.txt", true);
req.send();
};
window.onload = () => {
const loadButton = document.getElementById("load");
loadButton.onclick = loadData;
};
Then in file.txt
we put:
Hello world.
In the above example, we created an instance of the XMLHttpRequest
object to make a GET request to our server, which is a local web server that servers the file.txt
file. We call the open
function to start the request, The first argument is the HTTP request method, which can be get
, post
, put
, patch
or delete
. The second argument is the URL or relative path to your server side resource. The third argument is for setting whether the HTTP request happens asynchronously. Usually, it should be true
since we do not want to hold up other parts of our JavaScript code from loading. Also, we set the req.onload
listener function to get the response from the server and then populate the result. In reqListener
function, we get the response
from the server when it’s available and then populate the content of the element with ID content
with it. This happens after we call end send
function on the req
object.
Once we have these files we should get ‘Hello world’ when we click ‘Load Data’, we should get ‘Hello world.’ in our page.
This is the simplest case for making HTTP requests and then getting the result with AJAX. If we want to make requests that are more complex, then this is a very cumbersome way to do it. Fortunately, we have the Fetch API. to make a HTTP requests in our client side applications.
Promises
In JavaScript, a lot of code are asynchronous since it’s single threaded. To prevent holding up a program by code that finishes running in a indeterminate amount of time, we have to make a lot code asynchronous to prevent holding up the rest of the program from executing.
A promise is an object that represents an asynchronous process that finishes in an indeterminate amount of time. Before it’s executed then its status is pending. It can end in 2 states. If it’s successful, then the promise is resolved. A resolved promised is in the fulfilled state. Otherwise, it ends by by rejecting the promise. A promise is rejected if an error occurred. If an error occurred then the value that’s returned in the promise is ignored. In either case, when the promise finishes executing, a promise is in the settled status. That means settled status includes both fulfilled and error status.
The benefit of using promises for writing asynchronous code is that we can chain multiple promises together as if they are synchronous code. However, promises can only return other promises and they cannot return anything else. They are chained with the then
function, which takes a callback function which has the resolved value when the promise is fulfilled. When an errors occurred they can be handled with the catch
function at the end. To execute code no matter what the result of a promise is, we can chain the finally
function to the end of a promise.
To run an array of promises, we can use the Promise.all
function, which takes an array of promises.
An example of a promise would be something like:
const getTextPromise = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'file.txt', true);
xhr.onload = (response) => resolve(response.currentTarget.responseText);
xhr.onerror = () => reject(xhr.statusText);
xhr.send();
});
We wrapped the example we have above inside a promise and we resolve the response text instead of populating it in the DOM directly. To populate it in the DOM, we do:
getTextPromise
.then(responseText =>{
const content = document.getElementById("content");
content.innerHTML = responseText;
})
.catch(error =>{
alert(error.toString());
})
Make HTTP Requests with the Fetch API
The Fetch API makes use of promises to make asynchronous HTTP requests from the browser to a server. It provides us with an easy to use API for making requests. It lets us create HTTP requests with a Request
object, then when the server makes a response, then we get a Response
object. We make a request with the fetch
method. The fetch
method takes one mandatory, which includes all the data for making requests. Once the Response
object is returned then we can massage it to get it to the form we want and then populate it in our web pages.
Also, it’s aware of concepts like CORS, so we can make cross origin requests if the server allows by setting some options within the argument of the fetch
method. To use the fetch
method for making HTTP request, we can do it with an example. If we want to make a form for letting users subscribe to your email list, we can write the following code. Create a file calledindex.html
and add:
<html>
<head>
<title>Email List Subscribe Form</title>
</head>
<body>
<h1>Email List Subscribe Form</h1>
<form action="" name="nameForm" id="nameForm" method="post">
<label for="firstName">First Name: </label>
<input type="text" name="firstName" id="firstName" /><br />
<label for="lastName">Last Name: </label>
<input type="text" name="lastName" id="lastName" /><br />
<label for="email">Email: </label>
<input type="text" name="email" id="email" /><br />
<input type="submit" value="Subscribe" />
</form>
<script src="script.js"></script>
</body>
</html>
to add the form. Then create a script.js
file in the same folder and add:
const APIURL = "http://localhost:3000";
const subscribe = data => {
return fetch(`${APIURL}/subscribers\`, {
method: "POST",
mode: "cors",
cache: "no-cache",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(data)
})
.then(response => response.json());
};
window.onload = () => {
const nameForm = document.forms.nameForm;
nameForm.method = "post";
nameForm.target = "\_blank";
nameForm.action = "";
nameForm.addEventListener("submit", e => {
e.preventDefault();
const firstName = document.getElementById("firstName").value;
const lastName = document.getElementById("lastName").value;
const email = document.getElementById("email").value;
let errors = [];
if (!firstName) {
errors.push("First name is required.");
}
if (!lastName) {
errors.push("Last name is required.");
}
if (!email) {
errors.push("Email is required.");
}
if (!/\[^@\]+@\[^\.\]+\..+/.test(email)) {
errors.push("Email is invalid.");
} if (errors.length > 0) {
alert(errors.join(" "));
return;
}
subscribe({
firstName,
lastName,
email
}).then(response => {
alert(\`${response.firstName} ${response.lastName} has subscribed\`);
});
});
};
The subscribe
function is where we used the fetch
method. In there we pass in the URL of the server we are making the request to as the first argument. In the second argument, we set the method, which the HTTP request method for the request we want to make, which is POST. mode
should be CORS since we’re making a cross domain request. We don’t want any request caching so we set cache
to no-cache
. In headers
, we set Content-Type
to application/json
to make the requests data type send JSON. The body
is the request body of the request, which we create a JSON object since we are sending JSON to the server. Finally, we return response.json()
, which is a promise with the response if the request is successful sent back from the server and converted to JSON format.
To send something to the URL we point to we have to set up a web server. We first install the json-server
package by running npm i json-server
. Then, go to our project folder and run:
json-server --watch db.json
In db.json
, change the text to:
{
"subscribers": []
}
So we have the subscribers
endpoints defined in the requests.js
available.
Shorten Promise Code with Async and Await
The code above makes use of several promises. We have one for making the request, and then another one for converting it to JSON. Then finally, we make the alert
at the end. With the then
function, we make to put callback functions as an argument of all of our then
functions. This makes the code long is we have lots of promises. Instead, we can use the async
and await
syntax to replace the then
and its associated callbacks as follows. In script.js
, we put:
const APIURL = "http://localhost:3000";
const subscribe = async data => {
const response = await fetch(`${APIURL}/subscribers`, {
method: "POST",
mode: "cors",
cache: "no-cache",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(data)
});
return response.json();
};
window.onload = () => {
nameForm.method = "post";
nameForm.target = "_blank";
nameForm.action = "";
nameForm.addEventListener("submit", async e => {
e.preventDefault();
const firstName = document.getElementById("firstName").value;
const lastName = document.getElementById("lastName").value;
const email = document.getElementById("email").value;
let errors = \[\];
if (!firstName) {
errors.push("First name is required.");
}
if (!lastName) {
errors.push("Last name is required.");
}
if (!email) {
errors.push("Email is required.");
}
if (!/\[^@\]+@\[^\.\]+\..+/.test(email)) {
errors.push("Email is invalid.");
}
if (errors.length > 0) {
alert(errors.join(" "));
return;
}
try {
const response = await subscribe({
firstName,
lastName,
email
});
alert(`${response.firstName} ${response.lastName} has subscribed\`);
} catch (error) {
alert(error.toString());
}
});
};
We replaced the then
and callbacks with await
. Then we can assign the resolved values of each promise as variables. Note that if we use await
for our promise code then we have to put async
in the function signature like we did in the above example. To catch errors, instead of chaining the catch
function at the end, we use the catch
clause to do it instead. Also, instead of chaining the finally
function at the bottom to run code when a promise ends, we use the finally
clause after the catch
clause to do that instead.
async
functions always return promises, and cannot return anything else like any other function that uses promises.
With the Fetch API and async
and await
syntax, we can make HTTP requests to servers much more easily than using the old XMLHttpRequest
object. This is why now more and more web applications are using AJAX instead of refreshing the whole page on server side to get data.