如何知道何时取消页面重定向或刷新操作,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 对象中止它。

您可以 subscribeabort 事件以在请求被浏览器中止或通过 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。请看:

所以这种方法目前行不通。

您可以在此处找到有关该主题的更多信息:

可能的解决方法

检测服务端取消的请求

如果可以访问服务器,可以在服务器端检测到导航请求已经取消。

如果导航请求中止,只需向客户端发送通知。您可以使用 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;
        }));
    }
});

你总是可以结合最后两个选项来开发更稳定的方法。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...