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:
- When the page closes, a request is sent to the back end to release the resource.
- through
websocket
, 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, toreturnValue
Assigns a string;return ''
, in the callback functionreturn
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:
- and
Beacon 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 parallelkeepalive
Request, 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, because
keepalive
, sofetch
It’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