Make writing a habit together! This is my first day to participate in the “Gold Digging Day New Plan · April More text challenge”, click to see the details of the activity.


background

Recently there was a scenario in requirements where the resource needed to be released when the page was closed so that it could be used by others.

There are two initial scenarios:

  1. When the page closes, a request is sent to the back end to release the resource.
  2. throughwebsocket, the backend releases resources when the connection is disconnected.

On consideration, websocket was too heavy for this scenario alone, so the first option was chosen.

When deciding on a solution, I didn’t think it would be too hard. I thought Google Chrome would provide a page-closing API for developers to use.

After searching, we found two apis: beforeunload and unload

beforeunload

The beforeUnload event is triggered when the browser window closes or is refreshed. The current page will not be closed directly, you can click the OK button to close or refresh, or cancel the closure or refresh.

This event causes a dialog box to pop up when the page is leaving or refreshing, giving the user an indication. When the popup appears, the page can’t do anything except close the popup. Other pages can only be simple click browse operation, keyboard is not able to operate.

use

window.addEventListener('beforeunload', function (event) {
  // Cancel the event as stated by the standard.
  event.preventDefault();
  // Chrome requires returnValue to be set.
  event.returnValue = '';
});
Copy the code

For the sake of user security, custom messages to users are no longer supported by browsers. Scam sites often abuse this feature by prompting users to leave the page, making them think it was a mistake to leave the page.

To display the preventDefault dialog, call event.preventDefault() in the event, but browsers have different implementations as alternatives:

  • event.returnValue = '' , that is, to returnValueAssigns a string;
  • return '' , in the callback function return A string.

unload

An UNLOAD event is triggered when a document or a child resource is being unloaded.

What state is the document in when the Unload event fires after the beforeUnload event?

All resources exist, such as images, iframes, etc., but these resources are not visible to the user, and the interface interaction is not effective.

It is used the same as beforeunload, but confirmation boxes cannot be used in unload events because they are already being unloaded 🙁

window.addEventListener('unload', function(event) {
  console.log('unload');
});
Copy the code

The problem

But when I actually use it, I found several problems

Unable to get callback cancelled/confirmed by user

I can’t get a callback where the user clicks cancel/confirm on the confirmation box, so the request is sent regardless of whether the user cancels or confirms, and the confirmation dialog is meaningless.

The solution

The next best thing I can do is not show the confirmation box when the user closes, as long as the request is sent successfully, requirement is the most important :<

Cannot distinguish between refresh/close

I was naive to think that the browser would simply display the reload and exit headers in a confirmation dialog, but there must be an API to tell the difference. I can’t tell whether the user has refreshed or closed the page because both operations call beforeunload and unload.

After all, the user refreshing the page is a common operation, and I can’t release the resource just because the user refreshes the page.

Pseudo solution

There are many posts that say that the UNLOAD API is used when a page is closed. However, the unload API is also used when a page is refreshed.

╥﹏╥, there are some ways to compare hacks, such as by comparing the time between onbeforeUnload and onunload, but 100% success rate does not seem to be guaranteed.

The asynchronous request is cancelled, causing the request to fail to be sent

This is because asynchronous AJAX requests may or may not be sent by the browser when the page is unloaded.

Or you could add some time-consuming synchronization operations to the Unload event (such as a 10000 x 10000 double for loop) and allow enough time for the asynchronous AJAX to send successfully (but won’t the user kill you doing that?)

Solution 1

Use synchronous AJAX requests in the callback of events.

window.addEventListener('unload', function (event) {
  let xhr = new XMLHttpRequest();
  xhr.open('post', '/log', false);
  xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  xhr.send('foo=bar');
});
Copy the code

But Google Chrome already doesn’t allow XMLHTTPRequest() to synchronize during page closure. This rule applies to the beforeUnload, Unload, PageHide, and VisiBilityChange apis, which can be seen here.

Solution 2

To ensure that the page sends data to the server during uninstallation, the official recommendation is to use sendBeacon() or Fetch keep-alive

So let’s look at these two methods.

Beacon

This approach is primarily used to meet the needs of statistical and diagnostic code that typically tries to send data to the Web server before unloading documents.

The Beacon API has the following features:

  • A small amount of data is asynchronously transmitted through HTTP POST, which has good reliability
  • This request does not require a response and is guaranteed to be sent before the Unload state of the page starts to complete.
  • It does not block page unload and thus does not affect the loading of the next navigation
  • Support cross-domain
  • Custom headers are not supported

grammar

const result = navigator.sendBeacon(url, data);
Copy the code

Url: The URL of the request you want to send

Data: Optional. Data to be brought to the back end can be of type ArrayBuffer, ArrayBufferView, Blob, DOMString, FormData, or URLSearchParams

Result: sendBeacon returns the value true if the data was successfully enqueued, false otherwise. Note that the return value is to determine whether the transfer queue was successfully enqueued, not the return value of the request.

Use the sample

This solves the problem of the request being cancelled frequently when the page unloads

window.addEventListener('unload', function (event) {
  const data = {name: "beacon"};
  navigator.sendBeacon('/log', JSON.stringify(data));
});
Copy the code

Fetch keep-alive

The Keepalive property is used to tell the browser to hold the connection in the background and continue sending data when the page is unmounted. Fetch with the Keepalive flag is an alternative to the Navigator.sendbeacon () API.

Another typical scenario is when a user leaves a web page and submits some user behavior statistics to the server.

With keepalive enabled, the web page is closed and the request continues without interruption.

Use the sample

window.onunload = function() {
  fetch('/analytics', {
    method: 'POST',
    body: "statistics",
    keepalive: true
  });
};
Copy the code

Compared to the Beacon API, it has several advantages: it can customize request headers, not just POST requests…

Anyway, you just add an attribute to the Fetch, so just use it as a normal Fetch request.

Beacon and Fetch Keepalive restrictions

In addition to the API itself, there are some other limitations:

  • andBeacon API Again, only a small amount of data can be transmitted, and the sum is usually64 KBThis is so that requests can be completed quickly and in a timely manner, in other words, if multiple executions are performed in parallelkeepaliveRequest, the sum of data cannot exceed64 KBIf it exceeds, one is returnednetwork error, if you have questions about the amount of data transferred, you can stamp ithere 和 here.
  • If the document is unloaded, we can’t handle the server response, so in the example, becausekeepalive, sofetchIt’s gonna work, butSubsequent functionIt will not work properly.

However, it was found later that the underlying level of Beacon was realized through the FETCH API.

conclusion

Because is not good how to distinguish between a page refresh or shut down, after all, many habitual users will refresh the page, can’t because of refreshing backend request release resources, in the end we consult with product “friendly”, changed the way the implementation of release resources, perfect solved the demand, also have a deeper understanding to the apis.

The above is my shallow understanding, if there is wrong also forget the big guy pointed out.

Refer to the article

  • MDN – Navigator.sendBeacon()
  • w3c beacon
  • Navigator.sendbeacon () data size limit
  • MDN – unload
  • MDN – beforeunload