32
AJAX Demystified - What Is AJAX?
In Part 1 of this series, we laid the foundation for learning AJAX by digging deep into the history of web development at the time when AJAX was created. In this next part of the series, we will learn what AJAX really is.
We learned from the previous blog post that AJAX was created to solve the problem with the "annoying" delay in the responsiveness of a sovereign web application. An AJAX application, therefore, eliminates the start-stop-start-stop nature of interaction on the Web by implementing a collection of four technologies that complement one another: CSS, DOM, JavaScript, and XMLHttpRequest.
It's worth mentioning that three of the four technologies listed above ( CSS, DOM, JS ) have been collectively referred to as Dynamic HTML (DHTML). DHTML offered the ability to create interactive UI for web pages. However, it never overcame the issue of the full-page refresh since without communicating with the server, there was only so much it could do. AJAX makes considerable use of DHTML, but with the ability to make asynchronous requests to the server, AJAX has a lot more to offer.
XMLHttpRequest is a native API in JS that encapsulates the logic of sending HTTP requests without having to refresh a loaded web page - _ AJAX request. _ Even though developers rarely use XMLHttpRequest directly now, it's still the building block that works underneath many popular HTTP request modules such as Fetch and Axios.
The complete lifecycle of loading a document using XMLHttpRequest is as follows: _instantiate the XMLHttpRequest object, tell it to load a document, and then monitor that load process asynchronously using callback handlers. _
Now that you understand the high-level concept of AJAX, let's jump into some codes.π©βπ» Let's try to implement AJAX using Vanilla JS. Why would we want to do that if there are several frameworks out there that have out-of-the-box AJAX technology? It's because we want to learn "under the hood", remember?π
We will be implementing AJAX using Vanilla JS to build a simple web application as shown below. When the Retrieve button is clicked, a "Hello World!" message returned by the backend server via an AJAX call is displayed right below the button without a page refresh. Try it for yourself!π
A simple AJAX implementation takes four steps:
XMLHttpRequest
(aka XHR
) objectopen()
method of the XHR
to specify what kind of data you wantsend()
method to send the requestNow, let's look at some code. For this simple demo, we have a simple backend web server written in Node.js to process AJAX requests. For the UI on the frontend, we have a simple HTML and a script for the Retrieve button's event handling.
The code for our Node.js web server is defined in the
index.js
file located in the root directory.var http = require("http");
var fs = require("fs");
function render(path, contentType, fn) {
fs.readFile(__dirname + "/" + path, "utf-8", function (err, str) {
fn(err, str, contentType);
});
}
//create a server object:
http
.createServer(function (req, res) {
var httpHandler = function (err, str, contentType) {
if (err) {
console.log(err);
res.writeHead(500, { "Content-Type": "text/plain" });
res.end("An error has occured: " + err.message);
} else {
res.writeHead(200, { "Content-Type": contentType });
res.end(str);
}
};
if (req.url.indexOf("/scripts/") >= 0) {
console.log("Serving ajax.js");
render(req.url.slice(1), "application/javascript", httpHandler);
} else if (
req.headers["x-requested-with"] === "XMLHttpRequest" &&
req.headers["x-vanillaajaxwithoutjquery-version"] === "1.0"
) {
console.log("Processing AJAX request");
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ message: "Hello World!" }));
} else {
console.log("Serving index.html");
render("views/index.html", "text/html", httpHandler);
}
})
.listen(8080); //the server object listens on port 8080
Here, we're using the
http
module to create an HTTP server. For those of you that are not familiar with Node.js, here is a brief explanation of the code.The server is listening on Port
8080
. The callback function we pass to the createServer()
method is executed for each request that comes in. Whenever a new request is received, the request
event is called, providing two objects: a request (an http.IncomingMessage
object) and a response (an http.ServerResponse
object). The request
provides the request details through which we can access the request headers
and request data
. The response
is used to populate the data returning to the client.var http = require("http");
//create a server object:
http
.createServer(function (req, res) {
// callback to handle request is define here
})
.listen(8080); //the server object listens on port 8080
Let's look at the callback function used to handle requests.
if (req.url.indexOf("/scripts/") >= 0) {
render(req.url.slice(1), "application/javascript", httpHandler);
} else if (
req.headers["x-requested-with"] === "XMLHttpRequest" &&
req.headers["x-vanillaajaxwithoutjquery-version"] === "1.0"
) {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ message: "Hello World!" }));
} else {
render("views/index.html", "text/html", httpHandler);
}
This block of code checks the request URL to determine how the app should respond.
scripts
directory, then the appropriate file is served with the application/javascript
content type. x-request-with
headers set to XMLHttpRequest
indicates an AJAX request. In this case, we simply return a "Hello World!" text message.index.html
file is served.We haven't discussed the
render()
and httpHandler()
functions yet, so let's do that now. The render()
function asynchronously reads the contents of the requested file. It accepts the httpHandler()
callback function, which is used to check for errors and write the content of the file to the response with the appropriate HTTP status code if everything looks good.function render(path, contentType, fn) {
fs.readFile(__dirname + "/" + path, "utf-8", function (err, str) {
fn(err, str, contentType);
});
}
<!--kg-card-end: markdown--><!--kg-card-begin: markdown-->
var httpHandler = function (err, str, contentType) {
if (err) {
console.log(err);
res.writeHead(500, { "Content-Type": "text/plain" });
res.end("An error has occured: " + err.message);
} else {
res.writeHead(200, { "Content-Type": contentType });
res.end(str);
}
};
Now, let's look at our frontend code, which contains the
/views/index.html
file and the /scripts/ajax.js
script used for the button event handling. The index.html
is straightforward. It references the ajax.js
script which is called when the user clicks the Retrieve button.<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Vanilla Ajax without jQuery</title>
</head>
<body>
<h1>Vanilla Ajax without jQuery</h1>
<button id="retrieve" data-url="/">Retrieve</button>
<p id="results"></p>
<script src="/scripts/ajax.js" async></script>
</body>
</html>
The
ajax.js
script is where most of the AJAX magic happens.(function () {
var retrieve = document.getElementById("retrieve"),
results = document.getElementById("results"),
toReadyStateDescription = function (state) {
switch (state) {
case 0:
return "UNSENT";
case 1:
return "OPENED";
case 2:
return "HEADERS_RECEIVED";
case 3:
return "LOADING";
case 4:
return "DONE";
default:
return "";
}
};
retrieve.addEventListener("click", function (e) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
console.log(
"Inside the onreadystatechange event with readyState: " +
toReadyStateDescription(xhr.readyState)
);
if (xhr.readyState === 4 && xhr.status === 200) {
if (xhr.responseType === "json") {
results.innerHTML = xhr.response.message;
} else {
results.innerHTML = JSON.parse(xhr.responseText).message;
}
}
};
xhr.open("GET", e.target.dataset.url, true);
xhr.responseType = "json";
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
xhr.setRequestHeader("x-vanillaAjaxWithoutjQuery-version", "1.0");
xhr.send();
});
})();
We followed the five steps to implement AJAX described in the previous sections:
Create an xhr
instance of the XMLHttpRequest
object using the XMLHttpRequest() constructor.
Use the open()
method of the XMLHttpRequest
object to specify the kind of data being requested. It takes three arguments:
Create a callback function to process the response. XHR
has two properties used to indicate a response from the server:
Finally, all we're left to do is send the request. But before we do that, we need to make sure we set the _ request headers _ accordingly to indicate that our request is indeed an AJAX request.
Let's tit it all together - frontend and backend. When we start the server by running
npm run start
, the server returns the rendered index.html
page to the browser. Since the index.html
file references the ajax.js
script, the browser issues another request to the server to get this file. Finally, when the user clicks the Retrieve button, the server returns the "Hello World!" message. This workflow can be observed via the server's terminal logger as shown below.Serving index.html
Serving ajax.js
Processing AJAX request
Below are some examples of AJAX implementations for your references:
32