20

What is and what is not possible to do inside the beforeunload callback ?

Is it possible to open an XHR/fetch and send data to the server ? If no, is it possible to just send data, without any success callback blocking ?

Is it possible to change the location of the page with window.location? How long does the function continue to execute?

window.addEventListener("beforeunload", function (event) {
    // code
});
Walle Cyril
  • 3,087
  • 4
  • 23
  • 55

2 Answers2

45

You can put anything you want inside of a "beforeunload" callback, you're just not guaranteed it will execute unless you use synchronous/blocking code.

Here's a blocking sleep function I'll use for demonstration purposes:

function sleep(delay) {
  var start = new Date().getTime();
  while (new Date().getTime() < start + delay);
}

Any synchronous, blocking code is guaranteed to execute to completion:

window.addEventListener("beforeunload", function (event) {
  console.log("Blocking for 1 second...");
  sleep(1000);
  console.log("Done!!");
});

Any async code (i.e. code with callbacks) like the code below will be queued on the event loop, but it may or may not finish executing depending on when your browser completes the "unload" action (e.g. close window, refresh page.) The time here really depends on your computer's performance. On my computer, for example, a timeout of 10ms with log statement still executes because my browser hasn't had time to refresh/close the page.

window.addEventListener("beforeunload", function (event) {
  setTimeout(() => {
    console.log("Timeout finished!"); // Odds are this will finish
  }, 10);
  console.log("Done!!");
});

A timeout of 100ms, however, is never executed:

window.addEventListener("beforeunload", function (event) {
  setTimeout(() => {
    console.log("Timeout finished!");
  }, 100);
  console.log("Done!!");
});

The only way to guarantee your function will run to completion is to make sure you're writing synchronous code that blocks the event loop as in the first example.

fny
  • 31,255
  • 16
  • 96
  • 127
  • 2
    Question, would adding a `while(!asyncCompleted);` blocker work? – Vic Mar 24 '17 at 20:36
  • 2
    Nope. The while loop will block the async event from ever completing, so you'll be trapped in an infinite loop. – fny Mar 24 '17 at 20:59
  • Interesting. What if you create a WebWorker? – Vic Mar 24 '17 at 21:03
  • Is it possible to send an XHR and sending data to the server ? Is it possible to change the location of the page ? – Walle Cyril Mar 24 '17 at 22:17
  • 1
    If a page fires its unload event, it means the user wants to go somewhere else. Why you would interfere with that?? I don't know. I mean, can you imagine mom walking in the room.... cmd + q, cmd + q, cmd + q ... mother disowns son. – Ryan Wheale Mar 25 '17 at 01:38
  • Do `event.preventDefault()` to prevent this from happening. –  Mar 25 '17 at 19:17
  • @WalleCyril It's currently possible to send a synchronous XMLHttpRequest if you force it to be synchronous, but this is deprecated in some browsers. Also, you can't change the page location since the location change gets queued as an async event in most browsers (i.e. the browser doesn't transition until a connection is established.) – fny Mar 26 '17 at 01:41
  • 6
    @RyanWheale The main use case for this is detecting when the user has filled in a form but not submitted it, or done some other action without saving, and we're trying to help. – jezmck Aug 29 '18 at 07:36
  • 3
    So in your first example, if I wanted to write code that's guaranteed to run, where would I put it? I see that you've made a sleep function which blocks the event loop, but which console.log is guaranteed to run? Where would you put something guaranteed to complete, such as submitting a GET request? – Kyle Vassella Jul 09 '19 at 15:22
  • This effectively mean that you cannot be sure to save something to indexedDB before exiting. Is there any update about this? – Leo May 29 '20 at 13:28
  • Is this behavior documented somewhere? – Leo May 29 '20 at 15:22
  • What if we await the async function? Something like `await new Promise(r => setTimeout(r, 1000));` – WSD Dec 15 '22 at 07:36
10

To help some of the people in the comments on the first answer, check out this functionality to make an XHR request during the unload event: https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon

Anthony
  • 13,434
  • 14
  • 60
  • 80