XMLHTTPRequest (‘Ajax’), and how you can get started using it.
If you know why you want to use Beacon already, feel free to jump directly to the Getting Started section.
What Is The Beacon API For?
The Beacon API is used for sending small amounts of data to a server without waiting for a response. That last part is critical and is the key to why Beacon is so useful — our code never even gets to see a response, even if the server sends one. Beacons are specifically for sending data and then forgetting about it. We don’t expect a response and we don’t get a response.
Think of it like a postcard sent home when on vacation. You put a small amount of data on it (a bit of “Wish you were here” and “The weather’s been lovely”), put it in the mailbox, and you don’t expect a response. No one sends a return postcard saying “Yes, I do wish I was there actually, thank you very much!”
For modern websites and applications, there’s a number of use cases that fall very neatly into this pattern of send-and-forget.
Tracking Stats And Analytics Data
There’s no reason we couldn’t also cover the sort of mundane tasks often handled by Google Analytics, reporting on the user themselves and the capability of their device and browser. If the user has a logged in session, you could even tie those stats back to a known individual. Whatever data you gather, you can send it back to the server with Beacon.
Debugging And Logging
In fact, any logging task can usefully be performed using Beacon, be that creating save-points in a game, collecting information on feature use, or recording results from a multivariate test. If it’s something that happens in the browser that you want the server to know about, then Beacon is likely a contender.
Can’t We Already Do This?
I know what you’re thinking. None of this is new, is it? We’ve been able to communicate from the browser to the server using
XMLHTTPRequest for more than a decade. More recently we also have the Fetch API which does much the same thing with a more modern promise-based interface. Given that, why do we need the Beacon API at all?
The key here is that because we don’t get a response, the browser can queue up the request and send it without blocking execution of any other code. As far as the browser is concerned, it doesn’t matter if our code is still running or not, or where the script execution has got to, as there’s nothing to return it can just background the sending of the HTTP request until it’s convenient to send it.
That might mean waiting until CPU load is lower, or until the network is free, or even just sending it right away if it can. The important thing is that the browser queues the beacon and returns control immediately. It does not hold things up while the beacon sends.
To understand why this is a big deal, we need to look at how and when these sorts of requests are issued from our code. Take our example of an analytics logging script. Our code may be timing how long the users spend on a page, so it becomes critical that the data is sent back to the server at the last possible moment. When the user goes to leave a page, we want to stop timing and send the data back home.
Typically, you’d use either the
beforeunload event to execute the logging. These are fired when the user does something like following a link on the page to navigate away. The trouble here is that code running on one of the
unload events can block execution and delay the unloading of the page. If unloading of the page is delayed, then the loading next page is also delayed, and so the experience feels really sluggish.
Keep in mind how slow HTTP requests can be. If you’re thinking about performance, typically one of the main factors you try to cut down on is extra HTTP requests because going out to the network and getting a response can be super slow. The very last thing you want to do is put that slowness between the activation of a link and the start of the request for the next page.
Beacon gets around this by queuing the request without blocking, returning control immediately back to your script. The browser then takes care of sending that request in the background without blocking. This makes everything much faster, which makes users happier and lets us all keep our jobs.
So we understand what Beacon is, and why we might use it, so let’s get started with some code. The basics couldn’t be simpler:
let result = navigator.sendBeacon(url, data);
The result is boolean,
true if the browser accepted and queued the request, and
false if there was a problem in doing so.
navigator.sendBeacon takes two parameters. The first is the URL to make the request to. The request is performed as an HTTP POST, sending any data provided in the second parameter.
The data parameter can be in one of several formats, all if which are taken directly from the Fetch API. This can be a
URLSearchParams — basically any of the body types used when making a request with Fetch.
I like using
FormData for basic key-value data as it’s uncomplicated and easy to read back.
// URL to send the data to let url = '/api/my-endpoint'; // Create a new FormData and add a key/value pair let data = new FormData(); data.append('hello', 'world'); let result = navigator.sendBeacon(url, data); if (result) console.log('Successfully queued!'); else console.log('Failure.');
Support in browsers for Beacon is very good, with the only notable exceptions being Internet Explorer (works in Edge) and Opera Mini. For most uses, that should be fine, but it’s worth testing for support before trying to use
That’s easy to do:
if (navigator.sendBeacon) // Beacon code else // No Beacon. Maybe fall back to XHR?
If Beacon isn’t available and your request is important, you could fall back to a blocking method such as XHR. Depending on your audience and purpose, you might equally choose to not bother.
An Example: Logging Time On A Page
To see this in practice, let’s create a basic system to time how long a user stays on a page. When the page loads we’ll note the time, and when the user leaves the page we’ll send the start time and current time to the server.
As we only care about time spent (not the actual time of day) we can use
performance.now() to get a basic timestamp as the page loads:
let startTime = performance.now();
If we wrap up our logging into a function, we can call it when the page unloads.
let logVisit = function() // Test that we have support if (!navigator.sendBeacon) return true; // URL to send the data to, e.g. let url = '/api/log-visit'; // Data to send let data = new FormData(); data.append('start', startTime); data.append('end', performance.now()); data.append('url', document.URL); // Let's go! navigator.sendBeacon(url, data); ;
Finally, we need to call this function when the user leaves the page. My first instinct was to use the
unload event, but Safari on a Mac seems to block the request with a security warning, so
beforeunload works just fine for us here.
When the page unloads (or, just before it does) our
logVisit() function will be called and provided the browser supports the Beacon API our beacon will be sent.
(Note that if there is no Beacon support, we return
true and pretend it all worked great. Returning
false would cancel the event and stop the page unloading. That would be unfortunate.)
Considerations When Tracking
As so many of the potential uses for Beacon revolve around tracking of activity, I think it would be remiss not to mention the social and legal responsibilities we have as developers when logging and tracking activity that could be tied back to users.
We may think of the recent European GDPR laws as they related to email, but of course, the legislation relates to storing any type of personal data. If you know who your users are and can identify their sessions, then you should check what activity you are logging and how it relates to your stated policies.
Often we don’t need to track as much data as our instincts as developers tell us we should. It can be better to deliberately not store information that would identify a user, and then you reduce your likelihood of getting things wrong.
DNT: Do Not Track
In addition to legal requirements, most browsers have a setting to enable the user to express a desire not to be tracked. Do Not Track sends an HTTP header with the request that looks like this:
If you’re logging data that can track a specific user and the user sends a positive
DNT header, then it would be best to follow the user’s wishes and anonymize that data or not track it at all.
In PHP, for example, you can very easily test for this header like so:
if (!empty($_SERVER['HTTP_DNT'])) // User does not wish to be tracked ...
The Beacon API is a really useful way to send data from a page back to the server, particularly in a logging context. Browser support is very broad, and it enables you to seamlessly log data without negatively impacting the user’s browsing experience and the performance of your site. The non-blocking nature of the requests means that the performance is much faster than alternatives such as XHR and Fetch.
If you’d like to read more about the Beacon API, the following sites are worth a look.
- “W3C Beacon specification,” W3C Candidate Recommendation
- “MDN Beacon documentation,” MDN web docs, Mozilla
- “Browser support information,” caniuse.com