JavaScript, browsers, window close - send an AJAX request or run a script on window closing

Updated 2021

TL;DR

Beacon API is the solution to this issue (on almost every browser).

A beacon request is guaranteed to complete even if the user exits the page.

When should you trigger your Beacon request ?

This will depend on your usecase. If you are looking to catch any user exit, visibilitychange (not unload) is the last event reliably observable by developers in modern browsers.

NB: As long as implementation of visibilitychange is not consistent across browsers, it is easier to detect it via the lifecycle.js library.

# lifecycle.js (1K) for cross-browser compatibility
# https://github.com/GoogleChromeLabs/page-lifecycle

<script defer src="/path/to/lifecycle.js"></script>
<script defer>
lifecycle.addEventListener('statechange', function(event) {

  if (event.originalEvent == 'visibilitychange' && event.newState == 'hidden') {
    var URL = "https://example.com/foo";
    var data = "bar";

    navigator.sendBeacon(URL, data);
  }
});
</script>

Details

Beacon requests are guaranteed to run to completion even if the user leaves the page - switches to another app, etc - without blocking user workflow.

    var URL = "https://example.com/foo";
    var data = "bar";

    navigator.sendBeacon(URL, data);

The question is when to send your Beacon request. Especially if you want to wait until the last moment to send session info, analytics, etc.

It used to be common practice to send it during the unload event, but changes to page lifecycle management - driven by mobile UX - killed this approach. Today, most mobile workflows (switching to new tab, switching to the homescreen, switching to another app...) do not trigger the unload event at any point.

If you want to do things when a user exits your app/page, it is now recommended to use the visibilitychange event and check for transitioning from passive to hidden state.

document.addEventListener('visibilitychange', function() {
      
  if (document.visibilityState == 'hidden') {
    
     // send beacon request
  }

});

The transition to hidden is often the last state change that's reliably observable by developers (this is especially true on mobile, as users can close tabs or the browser app itself, and the beforeunload, pagehide, and unload events are not fired in those cases).

This means you should treat the hidden state as the likely end to the user's session. In other words, persist any unsaved application state and send any unsent analytics data.

Details of the Page lifecyle API are explained in this article.

However, implementation of the visibilitychange event, as well as the Page lifecycle API is not consistent across browsers.

Until browser implementation catches up, using the lifecycle.js library and page lifecycle best practices seems like a good solution.

# lifecycle.js (1K) for cross-browser compatibility
# https://github.com/GoogleChromeLabs/page-lifecycle

<script defer src="/path/to/lifecycle.js"></script>
<script defer>
lifecycle.addEventListener('statechange', function(event) {

  if (event.originalEvent == 'visibilitychange' && event.newState == 'hidden') {
    var URL = "https://example.com/foo";
    var data = "bar";

    navigator.sendBeacon(URL, data);
  }
});
</script>

For more numbers about the reliability of vanilla page lifecycle events (without lifecycle.js), there is also this study.


There are unload and beforeunload javascript events, but these are not reliable for an Ajax request (it is not guaranteed that a request initiated in one of these events will reach the server).

Therefore, doing this is highly not recommended, and you should look for an alternative.

If you definitely need this, consider a "ping"-style solution. Send a request every minute basically telling the server "I'm still here". Then, if the server doesn't receive such a request for more than two minutes (you have to take into account latencies etc.), you consider the client offline.


Another solution would be to use unload or beforeunload to do a Sjax request (Synchronous JavaScript And XML), but this is completely not recommended. Doing this will basically freeze the user's browser until the request is complete, which they will not like (even if the request takes little time).


1) If you're looking for a way to work in all browsers, then the safest way is to send a synchronous AJAX to the server. It is is not a good method, but at least make sure that you are not sending too much of data to the server, and the server is fast.

2) You can also use an asynchronous AJAX request, and use ignore_user_abort function on the server (if you're using PHP). However ignore_user_abort depends a lot on server configuration. Make sure you test it well.

3) For modern browsers you should not send an AJAX request. You should use the new navigator.sendBeacon method to send data to the server asynchronously, and without blocking the loading of the next page. Since you're wanting to send data to server before user moves out of the page, you can use this method in a unload event handler.

$(window).on('unload', function() {
    var fd = new FormData();
    fd.append('ajax_data', 22);
    navigator.sendBeacon('ajax.php', fd);
});

There also seems to be a polyfill for sendBeacon. It resorts to sending a synchronous AJAX if method is not natively available.

IMPORTANT FOR MOBILE DEVICES : Please note that unload event handler is not guaranteed to be fired for mobiles. But the visibilitychange event is guaranteed to be fired. So for mobile devices, your data collection code may need a bit of tweaking.

You may refer to my blog article for the code implementation of all the 3 ways.