Introduction
Many a times our applications require to send data to the server when the user closes the application window or navigates away from it. Analytics, page statistics could be areas where this data could be used. A very intuitive idea that strikes instantly is that we've to process the window.onunload or the window.onbeforeunload event. These events fire during the window unloading phase. However, things get messy if we were to perform such action asynchronously via raising an XMLHttpRequest. This article will focus on such a scenario and provide a novel feature which landed recently in Chrome Canary and Firefox Nightly to implement the same.
Use Case
So, we’ve two major events which fire during the window's last few moments of operation:
- window.onunload
- window.onbeforeunload
The use case is we're going to post a request to our server when the user closes the window or navigates away from it.
window.onunload = function(evt){
var _xhr = new XMLHttpRequest();
_xhr.onload = function(e){
if(_xhr.readyState == 4){
if(_xhr.status == 200){
// success
}
else{
// error
}
}
}
_xhr.onerror = function(error){
// request error
}
_xhr.open("POST", url, false); // an async request
_xhr.send();
}
The problem in this implementation is quite subtle and important to address. We've raised an asynchronous request to the server which will be processed and results returned to the client at some time in the future. But by the time the response reaches back to the response callback, the window has already unloaded and so are the associated resources with it. This causes a "function not found" error.
Till now, we've been solving this issue by raising a synchronous request instead so that the window doesn't unload till the request has been processed and results returned to the callback. This is fine from an implementation POV but is rather bad from performance POV. The delay in the window unload is rather palpable and if you were to navigate away to another page, then the navigation is slowed down because of the synchronous operation.
sendBeacon to the rescue
Recently has landed in Chrome canary and Firefox nightly, the sendBeacon API which allows developers to address this problem. The sendBeacon API allows developers to raise asynchronous requests to the servers.
window.addEventListener('unload', function() {
var status = navigator.sendBeacon("/log", analyticsData);
}, false);
The API takes 2 parameters:
- url - the url to POST data to
- data - The data which needs to be transmitted
And it returns a value which indicates whether the data (to be transferred) has been successfully queued by the browser or not.
There are a few operating guidelines for the sendBeaon API.
- Data must always be transmitted asynchronously to the server by the browser
- Browsers should always use the HTTP POST method for accessing the server
- Browsers should prioritize the transmission of the data according to network congestion and transmit data at the earliest possible opportunity.
Excerpts from W3 org on the behavior of sendBeacon API:
If the User Agent limits the amount of data that can be queued to be sent using this API and the size of data causes that limit to be exceeded, this method returns false. A return value of true implies the browser has queued the data for transfer. However, since the actual data transfer happens asynchronously, this method does not provide any information whether the data transfer has succeeded or not. The actual data transfer may occur after the page has unloaded. To be still an effective mechanism for developers, the User Agent should make the best effort to transmit the data including making multiple attempts to transmit the data in presence of transient network or server errors, even though it uses POST to transmit the data.
So, the catch is the request is still thrown asynchronously but doesn't demand a callback handler upon request processing. Requests are queued by the browser and when possible, they are sent. So, the best we can check is whether the request was queued successfully or not.
What’s the catch?
We should be aware that the browser will make multiple attempts to transmit data to the server asynchronously using the HTTP POST method. As we know that the POST method is non-idempotent i.e. the result of a POST request if made n times will NOT result in the same resource state on the server. Request methods like GET, HEAD etc. are perfectly idempotent as they always result in the same state of the resource on the server irrespective of the number of times they are accessed by the client.
One might need to put in some care to ensure that the data being sent using the sendBeacon API is not something which might disrupt the state of the server (if sent multiple times).