I have a Web Worker. I wish to make periodic network requests with it. One thing I particularly want is to make these requests even if the main JS execution thread is blocked (eg by a window.alert). I'm using Chrome 38.
However, when I attempt to make network requests in the worker, the requests appear to be blocked by the UI thread. Here is a contrived example to illustrate the problem:
base.js:
var worker = new Worker("/worker.js");
setTimeout(function() {
console.log("begin blocking");
var startDt = new Date();
var blockPeriod = 5000;
var a;
// Obviously we'd never actually do this, but this while loop
// is a convenient way to create the problem case (a blocked main
// thread).
while ((new Date() - startDt) < blockPeriod) {
a = 0;
}
console.log("stop blocking");
}, 3000);
worker.js:
var requestInterval = 1000;
var sendRequest = function() {
console.log("Send request interval");
var request = new XMLHttpRequest();
request.open("GET", "/ping", true);
request.onload = function() {
if (request.status === 200){
console.log(request.responseText)
} else {
console.log(request.status)
}
};
request.onerror = function() {
console.log("error")
};
request.send();
setTimeout(sendRequest, requestInterval);
}
sendRequest();
The result I'm seeing is that we see successful HTTP requests for three seconds, until the blocking begins. At this point, we don't see anything logged to the console until the blocking ends, at which point we see five "Send request interval"s followed by 5 logs of the response, like so:
Send request interval
{"pong": true}
Send request interval
{"pong": true}
Send request interval
{"pong": true}
Send request interval
{"pong": true}
begin blocking
stop blocking
5x Send request interval
5x {"pong": true}
Send request interval
{"pong": true}
I also see in my server logs that no requests are made in that blocking time, then those five requests are all received roughly simultaneously at the end of the blocking period.
Given that "Send request interval" occurs five times in a row, the worker is evidently continuing to execute: if it weren't, it wouldn't make it through to queue up the next iteration. I've also found that if I block by triggering a window.alert instead of spinning in a loop, I get the log messages from the beginning of sendRequest at 1 second intervals, and then get the response handler log messages in a large batch as soon as I stop blocking.
In Firefox, the background thread seems to stop entirely in this case (I don't get that same batch of five requests queued up during the blocked period). However, I'm only targeting Chrome in this case (and I ultimately want to use WebSockets which don't even work in Firefox Workers), so I'm not really interested in that.
All added together, this leads me to believe that there are some classes of activity in Web Workers which are blocked by the spawning thread, and some which are not (I originally saw this same behavior with WebSockets). Concretely, I'd like to know (if anyone does know):
What Worker activity is blocked by the main thread in Chrome?
Is there a way to work around this? I'd very much like to be able to able to establish a WebSocket connection in a Worker, and then continue to PING/PONG back and forth, even if something (such as an alert/confirm) does block the main thread.
Is this all nonsense, and am I just doing something stupid?
Your observation is correct. When the UI thread is blocked, network calls aren't dispatched.
Even worse, Chrome has the best behavior of the bunch. When a worker makes a XHR request when the UI thread is blocked:
Chrome: all requests are queued. The browser will not actually issue the requests until the UI thread unblocks. On the plus side, the worker thread is still free to run.
Firefox: new XMLHttpRequest() blocks until the UI thread unblocks.
IE: xhr.open() blocks until the UI thread unblocks.
While Chrome fortunately does not cause a worker thread to stop and wait (even though it won't get any data), Firefox and IE will cause a worker thread to wait on the UI thread when you try to make a XHR request.
There is no way to work around this; you're beholden to the browser to make requests on your behalf. I haven't done any testing with WebSockets, but they may deliver events even if the UI thread is blocked. At worst, the received messages would queue until the UI thread unblocks.
In case anyone stumbles across this, this behavior is confirmed as a bug (to the loose definition of "bug" as "does not behave as it ought to") in Blink, as of February 2015:
https://code.google.com/p/chromium/issues/detail?id=443374
I'm having the same issue, with a Web Worker that performs a sort of a keep-alive: it periodically pings the server, to inform that the page is still alive. I'm also using console.log in the worker, but i'm sure this is not the cause.
After some investigations, I can state that the problem can address two different situations:
a long-time UI operation is blocking main thread and worker's requests are not executed. This is the case of sample illustrated by heliotrope. As of September 2018, the problem occurs only on Firefox and IE, since Chrome and Edge can correctly handle activities on worker when the main thread is blocked.
To solve this issue, I'm thinking at sending a special ping to the server before starting any long-time operation, to inform that he won't receive anything from me, until the operation is finished.
a long-time asynchronous ajax call is being performed by main thread and worker's requests are queued. Some setting on the server is preventing from multiple ajax calls to run in parallel: other requests are queued until the first ajax call completes. The problem is server-specific, hence it does not depend on any browser in particular.
This problem is, in my case, due to ASP.NET session state locking: the ASP.NET pipeline will not process requests belonging to the same session concurrently but queues them, and executes them serially. Here is a detailed link: http://tech-journals.com/jonow/2011/10/22/the-downsides-of-asp-net-session-state.
Marking controller's session state as ReadOnly will solve the problem, while completely disabling session state in Web.config (<sessionState mode="Off" />) will seriously improve performance of the whole application.
Related
We have an ASP.Net WebForms application that uses SignalR (v2.4.1) to do some bi-directional communications between server and client. It's worked fine for years: connections are stable, hundreds of users use it, etc.
However, we've started to get sporadic reports of connection problems from across our client base, all reporting the same thing: if the browser (Chrome) session goes idle for more than 5 minutes, the connection drops in the background. All timers in the page stop being run regularly, which (amongst other things) stops "keepalives" stop being sent, and eventually the connection fails with the client-side error:
The client has been inactive since <date> and it has exceeded the inactivity timeout of 50000 ms. Stopping the connection.
Standard procedure after this would be to automatically restart the connection, but this doesn't do anything. If/when the user reactivates the page (e.g. by switching to the tab), everything starts to spring back into life, albeit with a closed SignalR connection.
After much investigation, it seems that we're being impacted by this change introduced in Chrome v88, where timers (setTimeouts) are severely restricted if
The page has been hidden for more than 5 minutes
The timer has been "chained" 5 or more times - I'm assuming this is similar to recursion, where the timer calls itself.
Page has been "silent" for 30 seconds
The 5 minutes/30 seconds condition fits with the reports we're getting. However, we're running pretty basic Javascript on our page: there are only two uses of setTimeout in our own code, neither of which could ever "chain" (recurse) onto themselves. We also cannot replicate the issue: it's happened to us in testing, but we can't make it happen reliably. Disabling this feature via chrome://flags/#intensive-wake-up-throttling seems to mitigate the issue - but of course, we can't make this a requirement to use our site.
The only other Javascript running on the site is jquery.signalR-2.4.1.js, and from the SignalR source, there are lots of setTimeouts in there. Could SignalR be impacted by this change in Chrome; perhaps when it tries to silently reconnect after a temporary network issue or some other unpredictable event?
If not, is there any way, in any browser or IDE, to track which timers have been launched (and, more importantly, "chained"), so we can see what could be triggering this restriction?
We're as well facing issues with our signalR (WebSockets as transport). We're not able to reproduce it in our lab. The HAR files of our customer and extended logging provided us only the information that the client "consuming only after following interesting groups" is not sending pings within the default 30 seconds needed to keep the connection. Therefore the server closes the connection. We added logs in the signalR client library and only saw the ping timer not being hit on time. No error, no nothing. (Client is JavaScript and the issue occurred on customer site in chrome 87 (throttling was implemented there already for half of the chrome users - https://support.google.com/chrome/a/answer/7679408#87))
And the world is slowly getting aware of "an issue": https://github.com/SignalR/SignalR/issues/4536
Our quick help for our customers will be to create an ET with a manual broadcast ping-pong mechanism from the server site and each client will have to answer. Avoiding being dependent on the JavaScript ping in the signalR library until a "better" solution or fix is provided.
As a workaround, javascript library that does the ping can be modified, to slightly change the way that it uses the timers. One of the conditions for intensive throttling is that the setTimeout()/setInterval() chain count is 5+. This can be avoided for recurring calls, by using a web worker. The main thread can post a dummy message to the web worker, which does nothing other than posting a dummy message back to the main thread. The subsequent setTimeout() call can be made on the message event from the web worker.
i.e.,
main_thread_ping_function :- doPing() -> post_CallMeBack_ToWebWorker()
web_worker :- onmessage -> post_CallingYouBack_ToMainThread()
main_thread :- web_worker.onmessage -> setTimeout(main_thread_ping_function, timeoutValue)
Since the setTimeout() is called on a message from web worker, rather than from the setTimout() execution flow, the chain length remains one, and thus no intensive throttling would be done by chrome 88+.
Note that, chained setTimeout() calls in a web worker are not throttled by chrome at the moment, and thus defining the timer functionality inside a web worker, and acting on the messages(to perform ping) from web worker, too solves the problem. However, if chrome developers decide to throttle the timers in web workers too, in the future, it gets broken again.
A utility(similar to java scheduled executor) which allows scheduling of callbacks using web workers, to avoid throttling, by context switching:
class NonThrottledScheduledExecutor {
constructor(callbackFn, initialDelay, delay) {
this.running = false;
this.callback = callbackFn;
this.initialDelay = initialDelay;
this.delay = delay;
};
start() {
if (this.running) {
return;
}
this.running = true;
// Code in worker.
let workerFunction = "onmessage = function(e) { postMessage('fireTimer'); }";
this.worker = new Worker(URL.createObjectURL(new Blob([workerFunction], {
type: 'text/javascript'
})));
// On a message from worker, schedule the next round.
this.worker.onmessage = (e) => setTimeout(this.fireTimerNow.bind(this), this.delay);
// Start the first round.
setTimeout(this.fireTimerNow.bind(this), this.initialDelay);
};
fireTimerNow() {
if (this.running) {
this.callback();
// dummy message to be posted to web worker.
this.worker.postMessage('callBackNow');
}
};
stop() {
if (this.running) {
this.running = false;
this.worker.terminate();
this.worker = undefined;
}
};
};
<button onclick="startExecutor()">Start Executor</button>
<button onclick="stopExecutor()">Stop Executor</button>
<div id="op"></div>
<script>
var executor;
function startExecutor() {
if (typeof(executor) == 'undefined') {
// Schedules execution of 'doThis' function every 2seconds, after an intial delay of 1 sec
executor = new NonThrottledScheduledExecutor(doThis, 1000, 2000);
executor.start();
console.log("Started scheduled executor");
}
}
function stopExecutor() {
if (typeof(executor) != 'undefined') {
executor.stop();
executor = undefined;
document.getElementById("op").innerHTML = "Executor stopped at " + l;
}
}
var l = 0;
function doThis() {
l = l + 1;
document.getElementById("op").innerHTML = "Executor running... I will run even when the my window is hidden.. counter: " + l;
}
</script>
Microsoft have released SignalR 2.4.2, which should address the issue natively and avoid the need for any manual workarounds.
Nuget package available here, and the list of fixed issues is here
I know that it does not solve the problem altogether with chrome, however, the new edge that uses chromium engine has added a few new settings to govern the timeouts (since it was affected too by the change). There is a new whitelisting option that gives at least the power to the users to decide which pages are excluded from this behavior. I honestly do believe that these setting will be added by google sooner or later. Until then we recommend our customers to switch to edge if they are affected.
You can find it in settings\system:
Javascript new Websocket(socketUrl) runs on potentially forever if the server refuses to open the websocket, and by design, the surrounding script gets no information on why the server refuses to open it. I have seen client browsers sending websocket requests every few seconds for more than 10 weeks (I suppose a tab has been left open and unused for this whole time).
I have implemented a change in the surrounding client script that detects (via a normal http request) when the websocket requests have become useless, and then closes the socket. From this point onwards, clients who reload their javascript files won't cause problems anymore.
However, I wonder if there is a way to definitely deal with those forgotten tabs in clients who don't ever reload, or if the only thing I can do is to basically treat them like malware by blocking excess requests in the load balancer (haproxy).
Lets say your websocket object is named, websocket_obj. Like:
websocket_obj = new Websocket(url); // url You know it
Then, you can close the socket, when you get an error. So,
websocket_obj.onerror = function(){
Websocket.close();
}
Here is the mdn_refs.
I'm trying to find out when a user left a specified page. There is no problem finding out when he used a link inside the page to navigate away but I kind of need to mark up something like when he closed the window or typed another URL and pressed enter. The second one is not so important but the first one is. So here is the question:
How can I see when a user closed my page (capture window.close event), and then... doesn't really matter (I need to send an AJAX request, but if I can get it to run an alert, I can do the rest).
Updated 2021
TL;DR
Beacon API is the solution to this issue (on almost every browser).
A beacon request is supposed 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, you can 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 supposed to run to completion even if the user leaves the page - switches to another app, etc - without blocking user workflow.
Under the hood, it sends a POST request along with the user credentials (cookies), subject to CORS restrictions.
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, app state, 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.
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.
Adblockers
Adblockers seem to have options that block sendBeacon requests.
Cross site requests
Beacon requests are POST requests that include cookies and are subject to CORS spec. More info.
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.
I also wanted to achieve the same functionality & came across this answer from Felix(it is not guaranteed that a request initiated in one of these events will reach the server).
To make the request reach to the server we tried below code:-
onbeforeunload = function() {
//Your code goes here.
return "";
}
We are using IE browser & now when user closes the browser then he gets the confirmation dialogue because of return ""; & waits for user's confirmation & this waiting time makes the request to reach the server.
Years after posting the question I made a way better implementation including nodejs and socket.io (https://socket.io) (you can use any kind of socket for that matter but that was my personal choice).
Basically I open up a connection with the client, and when it hangs up I just save data / do whatever I need. Obviously this cannot be use to show anything / redirect the client (since you are doing it server side), but is what I actually needed back then.
io.on('connection', function(socket){
socket.on('disconnect', function(){
// Do stuff here
});
});
So... nowadays I think this would be a better (although harder to implement because you need node, socket, etc., but is not that hard; should take like 30 min or so if you do it first time) approach than the unload version.
The selected answer is correct that you can't guarantee that the browser sends the xhr request, but depending on the browser, you can reliably send a request on tab or window close.
Normally, the browser closes before xhr.send() actually executes. Chrome and edge look like they wait for the javascript event loop to empty before closing the window. They also fire the xhr request in a different thread than the javascript event loop. This means that if you can keep the event loop full for long enough, the xhr will successfully fire. For example, I tested sending an xhr request, then counting to 100,000,000. This worked very consistently in both chrome and edge for me. If you're using angularjs, wrapping your call to $http in $apply accomplishes the same thing.
IE seems to be a little different. I don't think IE waits for the event loop to empty, or even for the current stack frame to empty. While it will occasionally correctly send a request, what seems to happen far more often (80%-90% of the time) is that IE will close the window or tab before the xhr request has completely executed, which result in only a partial message being sent. Basically the server receives a post request, but there's no body.
For posterity, here's the code I used attached as the window.onbeforeunload listener function:
var xhr = new XMLHttpRequest();
xhr.open("POST", <your url here>);
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
var payload = {id: "123456789"};
xhr.send(JSON.stringify(payload));
for(var i = 0; i < 100000000; i++) {}
I tested in:
Chrome 61.0.3163.100
IE 11.608.15063.0CO
Edge 40.15063.0.0
Try this one. I solved this problem in javascript, sending ajax call to server on browse or tab closing. I had a problem with refreshing page because on onbeforeunload function including refreshing of the page. performance.navigation.type == 1 should isolate refresh from closing (on mozzila browser).
$(window).bind('mouseover', (function () { // detecting DOM elements
window.onbeforeunload = null;
}));
$(window).bind('mouseout', (function () { //Detecting event out of DOM
window.onbeforeunload = ConfirmLeave;
}));
function ConfirmLeave() {
if (performance.navigation.type == 1) { //detecting refresh page(doesnt work on every browser)
}
else {
logOutUser();
}
}
$(document).bind('keydown', function (e) { //detecting alt+F4 closing
if (e.altKey && e.keyCode == 115) {
logOutUser();
}
});
function logOutUser() {
$.ajax({
type: "POST",
url: GWA("LogIn/ForcedClosing"), //example controller/method
async: false
});
}
Im agree with Felix idea and I have solved my problem with that solution and now I wanna to clear the Server Side solution:
1.send a request from client side to server
2.save time of the last request recived in a variable
3.check the server time and compare it by the variable of last recived
request
4.if the result is more than the time you expect,start running the
code you want to run when windows closed...
Use:
<body onUnload="javascript:">
It should capture everything except shutting down the browser program.
Is these an API symmetric to Server-Sent Event to generate fire-and-forget events from browser to server? I know how to not reply to a request on the server side, but how to tell the browser that it does not need to wait for a reply?
The goal here is to save resources on the client side, say you want to send 10k events to the server as fast as possible, not caring about what the sever replies.
Edit: While mostly irrelevant to the question, here is some background about the project I'm working on which would make use of an "AJAX fire-and-forget". I want to build a JavaScript networking library for Scala.js that will have as one of its applications to be the transport layer between Akka actors on the JVM and on a browser (compiled with Scala.js). When WebSockets are not available I want to have some sort of fallback, and having a pending connection for the duration of a round trip on each JS->JVM message is not acceptable.
As you have asked for "how to tell the browser that it does not need to wait for a reply?"
I assume that you do not want to process the server reply.
in such case, it is better to utilize one pixel image response trick which is implemented by Google for analytics and tracking, and many other such services.
More details here
The trick is to create new image using javascript and set src property, the browser will immediately fire the request for image and browser can parallelly request form multiple such requests.
var image = new Image();
image.src = "your-script.php?id=123&other_params=also";
PROs:
easy to implement
less load on server/client, then ajax request
CONs:
you can send only GET requests using this appproach.
Edit
For more references:
http://help.yahoo.com/l/us/yahoo/ywa/faqs/tracking/advtrack/3520294.html
https://support.google.com/dfp_premium/answer/1347585?hl=en
How to create and implement a pixel tracking code
Again they are using same technique of pixel image.
So, just to be clear, you're trying to use the XMLHttpRequest as a proxy for your network communication, which means you are 100% at the mercy of whatever XMLHttpRequest offers you, right?
My take is that if you're going to stick with XMLHttpRequest for this, you're going to have to just make peace with getting a server response. Just make the call asynchronously and have the response handled by a no-op function. Consider what somebody else suggested, using a queue on the server (or an asynchronous method on the server) so you return immediately to the client. Otherwise, I really think JavaScript is just the wrong tool for the job you're describing.
XMLHttpRequest is going to be a different implementation (presenting a more or less common interface contract) in every browser. I mean, Microsoft invented the thing, then the other browser makers emulated it, then voila, everybody started calling it Web 2.0. Point being, if you push too hard at the doughy center of XMLHttpRequest, you may get different behavior in different browsers.
XMLHttpRequest, as far as I know, strictly uses TCP (no UDP option), so at the very least your client is going to receive a TCP ACK from the server. There is no way to tell the server not to respond at that level. It's baked into the TCP/IP network stack.
Additionally, the communication uses the HTTP protocol, so the server will respond with HTTP headers... right? I mean, that is simply the way the protocol is defined. Telling HTTP to be something different is kind of like telling a cat to bark like a chicken.
Even if you could cancel the request on the client side by calling abort() on XMLHttpRequest, you're not cancelling it on the server side. To do so, even if it were possible with XMLHttpRequest, would require an additional request sent all the way to the server to tell it to cancel the response to the preceding request. How does it know which response to cancel? You'd have to manage request id's of some kind. You would have to be resilient to out-of-order cancellation requests. Complicated.
So here's a thought (I'm just thinking out loud): Microsoft's XMLHttpRequest was based at least in spirit on an even earlier Microsoft technology from the Visual Interdev days, which used a Java applet on the client to asynchronously fire off a request to the server, then it would pass control to your preferred JavaScript callback function when the response showed up, etc. Pretty familiar.
That Java async request thing got skewered during the whole Sun vs. Microsoft lawsuit fiasco. I heard rumors that a certain original Microsoft CEO would blow a gasket any time he learned about Microsoft tech being implemented using Java, and kill the tech. Who knows? I was unhappy when that capability disappeared for a couple of years, then happy again when XMLHttpRequest eventually showed up.
Maybe you see where I'm going, here... :-)
I think perhaps you're trying to squeeze behavior out of XMLHttpRequest that it just isn't built for.
The answer might be to just write your own Java applet, do some socket programming and have it do the kind communications you want to see from it. But then, of course, you'll have issues with people not having Java enabled in their browsers, exacerbated by all the recent Java security problems. So you're looking at code-signing certificates and so on. And you're also looking at issues that you'll need to resolve on the server side. If you still use HTTP and work through your web server, the web server will still want to send HTTP responses, which will still tie up resources on the server. You could make those actions on the server asynchronous so that TCP sockets don't stay tied up longer than necessary, but you're still tying up resources on the server side.
I managed to get the expected behavior using a very small timeout of 2ms. The following call is visible by the server but the connection is closed on the client side before any reply from the server:
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState == 2) {
alert("Response header recived, it was not a fire-and-forget...");
}
};
xhr.open("POST", "http://www.service.org/myService.svc/Method", true);
xhr.timeout = 2;
xhr.send(null);
This is not fully satisfactory because the timeout may change between browser/computers (for instance, 1ms does not work on my setup). Using a large timeout in the order of 50ms means that the client might hit the limit of maximum concurrent opened connections (6 on my setup).
Using XMLHttpRequest to send an async request (i.e. where you don't care if it succeeds or what the response is:
var req = new XMLHttpRequest();
req.open('GET', 'http://my.url.goes.here.com');
req.send();
You can do much the same thing with an Image object, too, btw:
new Image().src = 'http://my.url.goes.here.com';
The Image approach works particularly well if you're making cross-domain requests, since Images aren't subject to same-origin security restrictions the way XHR requests are. (BTW, it's good practice but not essential to have your endpoint return a 1x1 pixel PNG or GIF response with the appropriate Content-Type, to avoid browser console warnings like 'Resource interpreted as Image but transferred with MIME type text/html'.)
It sounds like you're trying to solve the wrong problem. Instead of dealing with this on the client, why not handle this on the server side.
Take the message from the client and put a message on a service bus or store the data in a database and return to the client. Depending on your stack and architecture, this should be fairly simple and very fast. You can process the message out of band, either a second service listens to the message bus and processes the request, or some sort of batch processor can come along later and process the records in the database.
You won't have the same level of fine-grained control of the connection with XHR as with WebSockets. Ultimately, it's the browser that manages the HTTP connection lifecycle.
Instead of falling back from WebSockets to discrete XHR connections, maybe you can store and batch your events. For instance:
Client JS
function sendMessage(message) {
WebSocketsAvailable ? sendWithWebSockets(message) : sendWithXHR(message);
}
var xhrQueue = [];
function sendWithXHR(message) {
xhrQueue.push({
timestamp: Date.now(), // if this matters
message: message
});
}
function flushXhrQueue() {
if (xhrQueue.length) {
var req = new XMLHttpRequest();
req.open('POST', 'http://...');
req.onload = function() { setTimeout(flushXhrQueue, 5000); };
// todo: needs to handle errors, too
req.send(JSON.stringify(xhrQueue));
xhrQueue = [];
}
else {
setTimeout(flushXhrQueue, 5000);
}
}
setTimeout(flushXhrQueue, 5000);
On the server, maybe you can have two endpoints: one for WebSockets and one for XHR. The XHR handler deserialises the JSON queue object and calls (once per message) the same handler used by the WebSockets handler.
Server pseudo-code
function WSHandler(message) {
handleMessage(message, Date.now());
}
function XHRHandler(jsonString) {
var messages = JSON.parse(jsonString);
for (var messageObj in messages) {
handleMessage(messageObj.message, messageObj.timestamp);
}
}
function handleMessage(message, timestamp) {
...
}
I'm trying to find out when a user left a specified page. There is no problem finding out when he used a link inside the page to navigate away but I kind of need to mark up something like when he closed the window or typed another URL and pressed enter. The second one is not so important but the first one is. So here is the question:
How can I see when a user closed my page (capture window.close event), and then... doesn't really matter (I need to send an AJAX request, but if I can get it to run an alert, I can do the rest).
Updated 2021
TL;DR
Beacon API is the solution to this issue (on almost every browser).
A beacon request is supposed 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, you can 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 supposed to run to completion even if the user leaves the page - switches to another app, etc - without blocking user workflow.
Under the hood, it sends a POST request along with the user credentials (cookies), subject to CORS restrictions.
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, app state, 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.
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.
Adblockers
Adblockers seem to have options that block sendBeacon requests.
Cross site requests
Beacon requests are POST requests that include cookies and are subject to CORS spec. More info.
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.
I also wanted to achieve the same functionality & came across this answer from Felix(it is not guaranteed that a request initiated in one of these events will reach the server).
To make the request reach to the server we tried below code:-
onbeforeunload = function() {
//Your code goes here.
return "";
}
We are using IE browser & now when user closes the browser then he gets the confirmation dialogue because of return ""; & waits for user's confirmation & this waiting time makes the request to reach the server.
Years after posting the question I made a way better implementation including nodejs and socket.io (https://socket.io) (you can use any kind of socket for that matter but that was my personal choice).
Basically I open up a connection with the client, and when it hangs up I just save data / do whatever I need. Obviously this cannot be use to show anything / redirect the client (since you are doing it server side), but is what I actually needed back then.
io.on('connection', function(socket){
socket.on('disconnect', function(){
// Do stuff here
});
});
So... nowadays I think this would be a better (although harder to implement because you need node, socket, etc., but is not that hard; should take like 30 min or so if you do it first time) approach than the unload version.
The selected answer is correct that you can't guarantee that the browser sends the xhr request, but depending on the browser, you can reliably send a request on tab or window close.
Normally, the browser closes before xhr.send() actually executes. Chrome and edge look like they wait for the javascript event loop to empty before closing the window. They also fire the xhr request in a different thread than the javascript event loop. This means that if you can keep the event loop full for long enough, the xhr will successfully fire. For example, I tested sending an xhr request, then counting to 100,000,000. This worked very consistently in both chrome and edge for me. If you're using angularjs, wrapping your call to $http in $apply accomplishes the same thing.
IE seems to be a little different. I don't think IE waits for the event loop to empty, or even for the current stack frame to empty. While it will occasionally correctly send a request, what seems to happen far more often (80%-90% of the time) is that IE will close the window or tab before the xhr request has completely executed, which result in only a partial message being sent. Basically the server receives a post request, but there's no body.
For posterity, here's the code I used attached as the window.onbeforeunload listener function:
var xhr = new XMLHttpRequest();
xhr.open("POST", <your url here>);
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
var payload = {id: "123456789"};
xhr.send(JSON.stringify(payload));
for(var i = 0; i < 100000000; i++) {}
I tested in:
Chrome 61.0.3163.100
IE 11.608.15063.0CO
Edge 40.15063.0.0
Try this one. I solved this problem in javascript, sending ajax call to server on browse or tab closing. I had a problem with refreshing page because on onbeforeunload function including refreshing of the page. performance.navigation.type == 1 should isolate refresh from closing (on mozzila browser).
$(window).bind('mouseover', (function () { // detecting DOM elements
window.onbeforeunload = null;
}));
$(window).bind('mouseout', (function () { //Detecting event out of DOM
window.onbeforeunload = ConfirmLeave;
}));
function ConfirmLeave() {
if (performance.navigation.type == 1) { //detecting refresh page(doesnt work on every browser)
}
else {
logOutUser();
}
}
$(document).bind('keydown', function (e) { //detecting alt+F4 closing
if (e.altKey && e.keyCode == 115) {
logOutUser();
}
});
function logOutUser() {
$.ajax({
type: "POST",
url: GWA("LogIn/ForcedClosing"), //example controller/method
async: false
});
}
Im agree with Felix idea and I have solved my problem with that solution and now I wanna to clear the Server Side solution:
1.send a request from client side to server
2.save time of the last request recived in a variable
3.check the server time and compare it by the variable of last recived
request
4.if the result is more than the time you expect,start running the
code you want to run when windows closed...
Use:
<body onUnload="javascript:">
It should capture everything except shutting down the browser program.