60

I have a web page that handles remote control of a machine through Ajax. When user navigate away from the page, I'd like to automatically disconnect from the machine. So here is the code:

window.onbeforeunload = function () {
  bas_disconnect_only();
}

The disconnection function simply send a HTTP GET request to a PHP server side script, which does the actual work of disconnecting:

function bas_disconnect_only () {
   var xhr = bas_send_request("req=10", function () {
   });
}

This works fine in FireFox. But with Chrome, the ajax request is not sent at all. There is a unacceptable workaround: adding alert to the callback function:

function bas_disconnect_only () {
   var xhr = bas_send_request("req=10", function () {
     alert("You're been automatically disconnected.");
   });
}

After adding the alert call, the request would be sent successfully. But as you can see, it's not really a work around at all.

Could somebody tell me if this is achievable with Chrome? What I'm doing looks completely legit to me.

Thanks,

Preview
  • 35,317
  • 10
  • 92
  • 112
lang2
  • 11,433
  • 18
  • 83
  • 133
  • Where's the "bas_send_request" function? – Pointy Feb 09 '11 at 14:17
  • 1
    Pointy: bas_send_request() is a simple wrapper that create a ajax request and send it. I don't think it necessary to show that kind of detail. – lang2 Feb 09 '11 at 14:55

10 Answers10

80

This is relevant for newer versions of Chrome.

Like @Garry English said, sending an async request during page onunload will not work, as the browser will kill the thread before sending the request. Sending a sync request should work though.

This was right until version 29 of Chrome, but on Chrome V 30 it suddenly stopped working as stated here.

It appears that the only way of doing this today is by using the onbeforeunload event as suggested here.

BUT NOTE: other browsers will not let you send Ajax requests in the onbeforeunload event at all. so what you will have to do is perform the action in both unload and beforeunload, and check whether it had already taken place.

Something like this:

var _wasPageCleanedUp = false;
function pageCleanup()
{
    if (!_wasPageCleanedUp)
    {
        $.ajax({
            type: 'GET',
            async: false,
            url: 'SomeUrl.com/PageCleanup?id=123',
            success: function ()
            {
                _wasPageCleanedUp = true;
            }
        });
    }
}


$(window).on('beforeunload', function ()
{
    //this will work only for Chrome
    pageCleanup();
});

$(window).on("unload", function ()
{
    //this will work for other browsers
    pageCleanup();
});
user3071284
  • 6,955
  • 6
  • 43
  • 57
Mohoch
  • 2,633
  • 1
  • 26
  • 26
  • @Sebas, if you can provide more details, maybe we could try and help. What exactly fails? is it called ata all? are you sure your request is synchronous? – Mohoch Jan 05 '14 at 08:49
  • @Mohoch, it suddendly stopped working. I suspect a new release of chrome creating the problem but I cannot tell for sure until I realize some more tests. I am using a framework, there is a boolean to set the ajax call to sync and it works very well all over the software, except in the onbeforeunload event of the window. (network error raised) – Sebas Jan 05 '14 at 17:08
  • Also, onunload is not even called by chrome, using addeventlistener or direct handler affectation – Sebas Jan 05 '14 at 17:09
  • As of today, I just did an Ajax post with onbeforeunload with firefox. – Lee Mar 28 '14 at 14:53
  • Is it correct to say that to this day there's no cross-browser reliable solution for this? – Alexandr Kurilin Sep 16 '14 at 03:07
  • how can i add this functionaality when user click only on leave the page button ... or how can i fire an event when user click on leave the page button any suggestion – Aitazaz Khan Jun 24 '15 at 15:48
  • @AitazazKhan, if I understand you question, you should use the **onunload** event handler. It will fire when the window unloads, which means the user has decided to leave the page and has clicked any confirmation buttons popped up by the **onbeforeunload** event. – Mohoch Jun 25 '15 at 13:41
  • can you give me a little example and how do i know that onunload has just fired out – Aitazaz Khan Jun 26 '15 at 23:16
  • 1
    @AitazazKhan, there is an example in the answer itself, using both onunload and onbeforeunload. You can also see [this page](https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload) for more explanations and examples. If I'm missing something in you question, please feel free to elaborate so I can pinpoint the exact problem. – Mohoch Jun 28 '15 at 07:47
  • 5
    Great. You could just write ... .on('unload beforeunload', ... and pack the function directly in there can't you? – GDY Apr 06 '17 at 09:43
  • How about "pagehide" to support iOS Safari? is that needed or does this cover that? Doesn't hurt to add it, was my thought. – Fischer Aug 17 '17 at 08:46
  • 3
    This is not supported on chrome 80 and above. I just ran into a issue and landed here. https://developers.google.com/web/updates/2019/12/chrome-80-deps-rems – Abhinav Kumar Feb 19 '20 at 05:01
44

I was having the same problem, where Chrome was not sending the AJAX request to the server in the window.unload event.

I was only able to get it to work if the request was synchronous. I was able to do this with Jquery and setting the async property to false:

$(window).unload(function () {
   $.ajax({
     type: 'GET',
     async: false,
     url: 'SomeUrl.com?id=123'
   });
});

The above code is working for me in IE9, Chrome 19.0.1084.52 m, and Firefox 12.

Preview
  • 35,317
  • 10
  • 92
  • 112
Garry English
  • 5,070
  • 1
  • 36
  • 23
31

Checkout the Navigator.sendBeacon() method that has been built for this purpose.

The MDN page says:

The navigator.sendBeacon() method can be used to asynchronously transfer small HTTP data from the User Agent to a web server.

This method addresses the needs of analytics and diagnostics code that typically attempt to send data to a web server prior to the unloading of the document. Sending the data any sooner may result in a missed opportunity to gather data. However, ensuring that the data has been sent during the unloading of a document is something that has traditionally been difficult for developers.

This is a relatively newer API and doesn't seems to be supported by IE yet.

Sparky
  • 4,769
  • 8
  • 37
  • 52
  • 3
    Note that some ad blockers (like uBlock Origin) block Navigator.sendBeacon out of some sense of tracking prevention. Your app cannot rely on it at all as a result. – Reid Aug 11 '17 at 21:29
  • sendBeacon is good for analytics, but you can't be sure of when it will be sent. For production, or must-work actions, I would not recomend it – vicovicovico Dec 20 '22 at 13:14
5

Synchronous XMLHttpRequest has been deprecated (Synchronous and asynchronous requests). Therefore, jQuery.ajax()'s async: false option has also been deprecated.

It seems impossible (or very difficult) to use synchronous requests during beforeunload or unload (Ajax Synchronous Request Failing in Chrome). So it is recommended to use sendBeacon and I definitely agree!

Simply:

window.addEventListener('beforeunload', function (event) {  // or 'unload'
    navigator.sendBeacon(URL, JSON.stringify({...}));

    // more safely (optional...?)
    var until = new Date().getTime() + 1000;
    while (new Date().getTime() < until);
});
ghchoi
  • 4,812
  • 4
  • 30
  • 53
4

Try creating a variable (Boolean preferably) and making it change once you get a response from the Ajax call. And put the bas_disconnect_only() function inside a while loop. I also had a problem like this once. I think this happens because Chrome doesn't wait for the Ajax call. I don't know how I fixed it and I haven't tried this code out so I don't know if it works. Here is an example of this:

var has_disconnected = false;
window.onbeforeunload = function () {
    while (!has_disconnected) {
        bas_disconnect_only();
        // This doesn't have to be here but it doesn't hurt to add it:
        return true;
    }
}

And inside the bas_send_request() function (xmlhttp is the HTTP request):

xmlhttp.onreadystatechange = function() {
    if (xmlhttp.readyState == 4 && xmlhttp.status == 200)
        has_disconnected = true;
}

Good luck and I hope this helps.

Metod Medja
  • 768
  • 6
  • 10
  • Thanks for the reply. That looks worth trying. But I've manage to find another solution that doesn't require the client to disconnect. Thanks all the same. – lang2 Feb 10 '11 at 08:55
2

I had to track any cases when user leave page and send ajax request to backend.

var onLeavePage = function() {
    $.ajax({
        type: 'GET',
        async: false,
        data: {val1: 11, val2: 22},
        url: backend_url
    });
};

/**
 * Track user action: click url on page; close browser tab; click back/forward buttons in browser
 */
var is_mobile_or_tablet_device = some_function_to_detect();
var event_name_leave_page = (is_mobile_or_tablet_device) ? 'pagehide' : 'beforeunload';
window.addEventListener(event_name_leave_page, onLeavePage);

/**
 * Track user action when browser tab leave focus: click url on page with target="_blank"; user open new tab in browser; blur browser window etc.
 */
(/*@cc_on!@*/false) ?  // check for Internet Explorer
    document.onfocusout = onLeavePage :
    window.onblur = onLeavePage;

Be aware that event "pagehide" fire in desktop browser, but it doesn't fire when user click back/forward buttons in browser (test in latest current version of Mozilla Firefox).

sNICkerssss
  • 6,312
  • 1
  • 24
  • 16
2
Try navigator.sendBeacon(...);

try {
        // For Chrome, FF and Edge
        navigator.sendBeacon(url, JSON.stringify(data));
    }
    catch (error)
    {
        console.log(error);
    }

    //For IE
    var ua = window.navigator.userAgent;
    var isIEBrowser = /MSIE|Trident/.test(ua);

    if (isIEBrowser) {
        $.ajax({
            url: url,
            type: 'Post',
            .
            .
            .
        });
    }
Amit Sharma
  • 133
  • 8
  • Please don't post only code as answer, but also provide an explanation what your code does and how it solves the problem of the question. Answers with an explanation are usually more helpful and of better quality, and are more likely to attract upvotes. – Pouria Hemi Nov 24 '20 at 12:18
2

I felt like there wasn't an answer yet that summarized all the important information, so I'm gonna give it a shot:

Using asynchronous AJAX requests is not an option because there is no guarantee that it will be sent successfully to the server. Browsers will typically ignore asynchronous requests to the server, that are made when leaving the page. It may, or may not, be sent. (Source)

As @ghchoi has pointed out, synchronous XMLHTTPRequests during page dismissal have been disallowed by Chrome (Deprecations and removals in Chrome 80). Chrome suggests using sendBeacon() instead.

According to Mozilla's documentation though, it is not reliable to use sendBeacon for unload or beforeunload events.

In the past, many websites have used the unload or beforeunload events to send analytics at the end of a session. However, this is extremely unreliable. In many situations, especially on mobile, the browser will not fire the unload, beforeunload, or pagehide events.

Check the documentation for further details: Avoid unload and beforeunload

Conclusion: Although Mozilla advises against using sendBeacon for this use case, I still consider this to be the best option currently available.

When I used sendBeacon for my requirements, I was struggling to access the data sent at the server side (PHP). I could solve this issue using FormData as recommended in this answer.

For the sake of completeness, here's my solution to the question:

window.addEventListener('beforeunload', function () {
    bas_disconnect_only();
});

function bas_disconnect_only () {
    const formData = new FormData();
    formData.append(name, value);
    navigator.sendBeacon('URL', formData);
}
Jan
  • 23
  • 4
1

I've been searching for a way in which leaving the page is detected with AJAX request. It worked like every time I use it, and check it with MySQL. This is the code (worked in Google Chrome):

    $(window).on("beforeunload", function () {
        $.ajax({
             type: 'POST',
             url: 'Cierre_unload.php',
             success: function () {
             }
        })
    })
0

To run code when a page is navigated away from, you should use the pagehide event over beforeunload. See the beforeunload usage notes on MDN.

On that event callback, you should use Navigator.sendBeacon(), as Sparky mentioned.

// use unload as backup polyfill for terminationEvent
const terminationEvent = "onpagehide" in self ? "pagehide" : "unload";

window.addEventListener(terminationEvent, (event) => {
    navigator.sendBeacon("https://example.com/endpoint");
});
Elijah Mock
  • 587
  • 8
  • 21