Almost all web pages make use of HTTP requests to load dynamic content after they have loaded. It might be using some library or simply using the XMLHttpRequest. It is also popularly known as AJAX request. Here we will explore the vanilla JS way to perform the request.
Let's see a beginner example for XMLHttpRequest:
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "request.php?param=true");
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var response = this.responseText;
//process response here
}
};
xhttp.send();
In the above example, a new XMLHttpRequest object is create in the first line and a request is opened to the given (relative) URL with GET method.
Then a new event listener is assigned to the readystatechange event for this object. And then in the last line the request is finally sent.
Here, you will note that the example uses readystatechange event. This type of code was popularly in use in earlier days of XMLHttpRequest. And it continues to be used in a lot of websites today as well. This approach could be useful, if you need to work on different ready states viz. 0 - Unsent, 1 - Opened, 2 - Headers Received, 3 - Loading and 4 - complete.
Inside the readystate event, you can see that the if condition checks for the ready state being 4 and the status returned should be 200, then only it should act. For all other states and status codes, it will simply skip. This event will get called whenever the ready state changes. This approach has one flaw - whenever an error occurs like CORS fails, or you fail to add an if condition for a status code like 500, it will skip!
And if you only want to work on this after all of the data has finished loading, you should instead use the load/error or loadend events. Here is another example:
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "request.php?param=true");
xhttp.onload = function() {
var response = this.responseText;
//process response here
};
xhttp.onerror = function(e) {
//some error occurred
console.log(e);
}
xhttp.send();
Now, this approach handles both load event and error event separately. So, whenever there is any error in processing the HTTP request and can be handled separately in the onerror event. Also, it will trigger once only i.e. at the end of request - and only one of them will be triggered. So, you can make sure that at the end of request, you definitely know what happened (and alert the user accordingly).
You can additionally add another event handler to your request - onabort (caused by xhttp.abort or user abort). It will handle the abort event which is the third case that might happen to a request. You can also bundle all these three in single code using loadend event. And then you can work in all three cases in single event.
Now, let's discuss the methods in a bit more detail:
open() Method
This method initiates the request to a given URL and accepts the following parameters:
xhttp.open(method, URL, [async, user, password]);
Here, the method and URL parameters are understood. And, async specifies whether to make the request asynchronous or not, while the user and password parameters are for basic HTTP authentication. All these three parameters are optional.
send() Method
This method performs the request. It optionally accepts data to be sent along with the request (more on this later).
This method is usually the last one to be called i.e. after all event assignment has been done. This method will assume control if the request has been made synchronously i.e. the javascript execution will be stopped at this point and will resume only after the request has either fialed or completed (no option to abort in this case).
Events
As it can be seen that XHR supports many events including - load, error, progress, loadend, timeout, readystatechange and so on.
Setting Timeout
Timeout can be set for the request by setting the timeout property of the request object to number of milliseconds to timeout after. Also make sure to add the additional ontimeout event to handle the scenario.
Simple Ping type request
If you want to send a request to server without needing to act on the result of the request, it can be done without assigning any event handler like this:
var xhttp = new XMLHttpRequest();
xhttp.open('GET', 'track.php?user=ABCD&data=click');
xhttp.send();
This will send the request at given URL.
Playing with headers
The headers for the request can be accessed and set using the following methods:
var hn = xhttp.getResponseHeader('Header-Name');
xhttp.getAllResponseHeaders();
//to set headers
xhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
Content-Type Header
This header is usually set when sending a body with the request. For example to send a POST request, the Content-Type header is set to application/x-www-form-urlencoded
and then the xhttp.send()
method attaches the request data as parameter in URL encoded form.
Accessing variables inside the event handlers of request
The event handlers can access the variables as defined by their scopes. The common variables are global variables, variables from the parent calling code and the variables created inside the event handler.
Consider the following example code:
var gl = 'I am a global variable';
function request() {
var pl = 'I am a parent variable';
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "request.php?param=true");
xhttp.onload = function() {
var ll = 'I am a local variable';
var response = this.responseText;
//process response here
console.log(gl);//I am a global variable
console.log(pl);//I am a parent variable
console.log(ll);//I am a local variable
};
xhttp.send();
}
As, it can be seen that the variables can be easily accessed inside the event handler body. Now see another example:
var gl = 'I am a global variable';
function request() {
var pl = 'I am a parent variable';
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "request.php?param=true");
xhttp.onload = function() {
var ll = 'I am a local variable';
var response = this.responseText;
//process response here
console.log(gl);//I am a global variable
console.log(pl);//I am a modified variable
console.log(ll);//I am a local variable
};
pl = 'I am a modified variable';
xhttp.send();
}
As it can be seen that in this case, the value of pl changed for the event handler as well. This scenario can also be seen in cases where you have to make multiple XHR requests in loops and with each iteration, the value of the variables change. And you often end up using the value that was set just before the event handler gets called.
To avoid this behaviour, a trick can be used by attaching the variable to the XHR object itself:
var gl = 'I am a global variable';
function request() {
var pl = 'I am a parent variable';
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "request.php?param=true");
xhttp.pl = pl;
xhttp.onload = function() {
var ll = 'I am a local variable';
var response = this.responseText;
//process response here
console.log(gl);//I am a global variable
console.log(this.pl);//I am a parent variable
console.log(ll);//I am a local variable
};
pl = 'I am a modified variable';
xhttp.send();
}
Here in above code you can see that the value for pl did not change as it was attached to the object itself. This way, even if you need to pass a few variables inside a loop, you can to so by attaching them to the XHR object itself and each request will have its own value.
Another approach to achieve this is using js closures.
Performing a POST Request
Look at the following code:
var xhttp = new XMLHttpRequest();
xhttp.open('POST', 'response.php');
xhttp.onload = () => {
//loaded
}
xhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhttp.send('user=AB%20CD&p=43&g=6');
In the above example, a new XHR request is being created to send a POST request to the given URL (in this case, response.php) and then the send method has the arguments that are to be sent. The response.php will receive in the POST request three variables - user, p and g with values 'AB CD', 43 and 6 respectively.
It is to be remembered for a POST request that:
- The request type is set to POST in open method (If GET is set, no data from the send method will be sent to the URL)
- Content-Type header is set to appropriate values for use in the request.
- Data to be sent using the send method should match with the Content-Type header
Performing a POST Request with JSON data
To perform a POST request with JSON data, all you need is to do is set the Content-Type header to application/json
and then attach the JSON object's string representation in the send method.
var xhttp = new XMLHttpRequest();
var o = {user: 'AB CD', p: 43, g: 6}
xhttp.open('POST', 'response.php');
xhttp.onload = () => {
//loaded
}
xhttp.setRequestHeader('Content-Type', 'application/json');
xhttp.send(JSON.stringify(o));
This code will perform the same request as seen in the earlier example, but using JSON as the request payload.
To parse this JSON data in the server, you will need to read the input stream, for example in PHP, you can do:
$data = file_get_contents('php://input');
$json = json_decode($data, true);
And then you can use the $json
object to access the data sent.
Performing CORS Requests
Cross origin requests are the requests performed from one domain (or subdomain) to another domain (or subdomain). These requests are often blocked for security reasons and can be unblocked by making some server-side changes to the responses.
For CORS, there is no additional requirements for the client side JS code. All changes are to be done in the server side code.
Whenever a request is initiated to another (sub-)domain, browsers generally attach the Origin
header. And they expect Access-Control-Allow-Origin
header with the current domain or a wildcard domain. If no such header is found, the request fails and triggers the onerror event.
You can add the Access-Control-Allow-Origin
header from server side with the list of domains and sub-domains that you want to allow to perform the request. Or you can optionally set the value to *
. However, the second approach is not recommended for private APIs and even if you are setting it, you must have some mechanism on the server side to check for the Origin header in the request before setting a wildcard allow header.
In PHP, you can do so using:
header('Access-Control-Allow-Origin: *');
And then your XHR request will start responding to your domain easily.
CORS with JSON data in POST
By default, CORS requests allow only GET, HEAD and POST requests that too with further limitation of Content-Type
being one of application.x-www-form-urlencoded
, multipart/form-data
and text/plain
.
But there are workarounds to make the requests work even with JSON content. To do so, simply add the following header:
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Headers: Content-Type');
And then your JSON requests will also succeed.
CORS failing even with Access-Control-Allow-Origin header
Sometimes, the request using CORS will fail even with Access-Control-Aloow-Origin header set to current domain. This might happen if the domain does not exactly match the domain given in the header. Do some testing and you will have the requests working smoothly across browsers.
The request may even fail if the response is 0B long. So, do check that the response received is not empty, or you will get CORS not allowed message. Check this in the network tools and you will know for sure what the problem is.