如何处理 Service Worker 安装事件中被拒绝的 Promise?

How do I handle a rejected Promise in a Service Worker install event?

我有一个案例,我需要从 Service Worker 安装事件中捕获一个被拒绝的承诺并显示一条错误消息。

我设置了一个名为 testsw.js 的测试 Service Worker,它会立即抛出一个被拒绝的承诺,如下所示:

console.log("Hello from SW");
self.addEventListener('install', function (evt) {
    evt.waitUntil(Promise.reject(new Error("Test")));
})

我正在使用以下代码注册 Service Worker:

navigator.serviceWorker.register('/testsw.js')
        .then(function (reg) {
             console.log("Service worker successfully registered.", reg);
        })
        .catch(function (err) {
            console.error("Service worker failed to register.", err);
        })

在这种情况下,catch 函数永远不会被调用,注册 Service worker 会导致此控制台输出:

testsw.js:1 Hello from SW
testsw.js:3 Uncaught (in promise) Error: Test
    at testsw.js:3
(anonymous) @ testsw.js:3
(index):468 Service worker successfully registered. ServiceWorkerRegistration

我已经尝试将被拒绝的 promise 包装在一个函数中并在 evt.waitUntil() 中调用它,以及抛出一个普通的错误,没有任何变化。 Service Worker 还是进入了冗余状态,这很好,但我仍然需要知道安装失败。

我是否误解了被拒绝的承诺在 Service Worker 安装中的工作方式?注册中是否存在某些错误配置,即使承诺被拒绝,它也会始终命中 then 块?如果做不到这一点,ServiceWorkerRegistration class 或其他地方是否有什么东西会告诉我们服务工作者未能安装或更新?

如果它有所不同,这在 Chrome 77 和 Edge 83 中进行了测试,但没有成功。

一旦加载脚本并注册 worker,注册承诺就会履行,无论安装是否失败(甚至可能不再需要安装)。查看是否安装失败,需要监听statechange events from the installing worker:

navigator.serviceWorker.register('/testsw.js').then(function (reg) {
     console.log("Service worker successfully registered.", reg);
     if (reg.installing) {
         reg.installing.onstatechange = function(e) {
             if (e.target.state == 'installed') {
                 console.log("Service worker successfully installed.");
             } else if (e.target.state == 'redundant') {
                 console.log("Service worker failed to install.");
             }
         };
     }
}).catch(function (err) {
    console.error("Service worker failed to register.", err);
});

如果您还想显示安装失败的 原因,看来您必须通过处理 install 处理程序中的错误并通过发送到原始页面的消息。也许也可以试试听 error events on the installing worker. You might however have to fire these manually, as unhandled promise rejections don't get caught automatically:

console.log("Hello from SW");
self.addEventListener('install', function (evt) {
    const installation = Promise.reject(new Error("Test"));
    evt.waitUntil(installation);
    installation.catch(err => {
        self.clients.matchAll({includeUncontrolled: true}).then(clients => {
            for (const client of clients)
                client.postMessage({type: 'error', message: err.message});
        });
    });
});