如何解决如何知道何时取消页面重定向或刷新操作,JavaScript 检测服务端取消的请求基于定时器的方法使用 Service Worker 跟踪导航请求加载持续时间
我目前正在处理一个页面,其中预加载器在onPageNavigating上显示给用户,并且当用户从浏览器中取消导航时,我想删除该预加载器。
我尝试过的。
- 创建一个倒数计时器
- 启动计时器onPageNavigating
- 如果计时器已过,则假定导航已取消
- 隐藏预载器
但是这种方法的问题在于导航时间可能会根据网络速度而变化,这对于我的用例来说是不可行的。
还有哪些其他选项可以实现,或者有可用的浏览器API?因为我无法通过搜索找到任何相关内容。
解决方法
TL;DR
目前有一种方法可以仅使用浏览器 API 在浏览器中接收或模拟 beforeunloadcancelled
事件。但由于浏览器中的错误仍未修复,它不起作用
下面我将描述主要方法、为什么它不起作用以及可能的解决方法。
仅基于浏览器 API 的方法
模拟beforeunloadcancelled
事件的主要思想是获取用户取消下一页导航请求的事件。您无法在主浏览器上下文中控制此类请求,但幸运的是,您可以从 Service Worker 上下文中进行控制。
如果您的 Service Worker 订阅了 fetch
事件,您将能够处理您的应用程序发出的所有导航请求:
// in Service Worker global scope
self.addEventListener('fetch',evt => {
if (evt.request.mode === 'navigate') {
console.log(`browser is navigating to the ${evt.request.url} resource now`);
}
});
根据specification,每个Request
对象都有属性signal
。此信号表示一个信号对象,它允许您与 DOM 请求(例如 Fetch)通信并在需要时通过 AbortController 对象中止它。
您可以 subscribe 到 abort
事件以在请求被浏览器中止或通过 AbortController 中止时获得通知。在该侦听器中 you can post message 到父窗口,其中包含导航请求已被取消的信息,这实际上意味着 beforeunloadcancelled
事件:
// in Service Worker global scope
self.addEventListener('fetch',evt => {
if (evt.request.mode === 'navigate') {
console.log(`browser is navigating to the ${evt.request.url} resource now`);
evt.request.signal.addEventListener('abort',async () => {
// exit early if we don't have an access to the client (e.g. if it's cross-origin)
if (!evt.clientId) return;
// get the client
const client = await self.clients.get(evt.clientId);
// exit early if we don't get the client (e.g.,if it's closed)
if (!client) return;
// send a message to the client
client.postMessage({
msg: 'navigation request has been canceled'
});
})
}
});
遗憾的是,存在那些 abort
事件未反映在 Service Worker 中的错误。 Chromium 或 Firefox 都没有正确实现将 AbortSignal 发送到 FetchEvent.request.signal
。请看:
- https://bugs.chromium.org/p/chromium/issues/detail?id=823697
- https://bugzilla.mozilla.org/show_bug.cgi?id=1394102
所以这种方法目前行不通。
您可以在此处找到有关该主题的更多信息:
- Abort Controller for Service Worker
- https://developers.google.com/web/updates/2017/09/abortable-fetch#in_a_service_worker
- https://github.com/w3c/ServiceWorker/issues/1544
- https://github.com/w3c/ServiceWorker/issues/1284
可能的解决方法
检测服务端取消的请求
如果可以访问服务器,可以在服务器端检测到导航请求已经取消。
如果导航请求中止,只需向客户端发送通知。您可以使用 WebSockets 发送通知。为了确保将通知发送到正确的客户端,可能会使用一些基于 client Id
的独特 cookie。
基于定时器的方法
正如您在问题中提到的,一种可能的解决方法是使用某种计时器:
window.addEventListener('beforeunload',() => {
showPreloader();
setTimeout(() => {
hidePreloader(); // we're assuming navigation has been cancelled
},TIMEOUT);
});
为了使这种方法更精确,您可以根据当前页面加载速度选择 TIMEOUT
值(可在 PerformanceNavigationTiming API 中找到):
const [initialNavigation] = window.performance.getEntriesByType('navigation');
const TIMEOUT = initialNavigation.duration * TIMEOUT_RATIO;
使用 Service Worker 跟踪导航请求加载持续时间
有一种方法可以通过 Service Worker 跟踪导航请求进度。导航请求完成后,您可以向父窗口发送通知。如果在此通知后没有立即发生导航,则表示导航已被有效取消并且可以隐藏预加载器:
// in Service Worker global scope
self.addEventListener('fetch',evt => {
if (evt.request.mode === 'navigate') {
console.log(`browser is navigating to the ${evt.request.url} resource now`);
evt.respondWith(fetch(evt.request).then(response => {
sendHidePreloaderNotification();
return response;
}));
}
});
你总是可以结合最后两个选项来开发更稳定的方法。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。