PWA系列 - Service Workers 异常处理

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 前端在写PWA页面时, 经常会遇到ServiceWorker线程启动失败,但能获取到的异常信息非常有限。本文详细分析一下ServiceWorker异常处理相关的问题。

前言

前端在写PWA页面时, 经常会遇到ServiceWorker注册失败, 或ServiceWorker执行具体业务逻辑时失败, 但又连不上devtools, 或者连上了而从devtools能获取到的异常信息非常有限。本文详细分析一下ServiceWorker异常处理相关的问题。

线程退出时机

ServiceWorker 规范中提到, Service workers may be started by user agents without an attached document and may be killed by the user agent at nearly any time, 即ServiceWorker线程可能在任意时间被浏览器停止,即使关联的文档还未关闭ServiceWorker线程也有可能已被停止。这种设计主要是为了降低ServiceWorker对资源(比如, 浏览器内存,手机电量)的消耗,但会为前端带来很大的麻烦,俗称“坑”。

一些可能的坑:

(1)ServiceWorker JS里面不能使用全局变量,如果需要全局状态,必须自己进行持久化,比如使用IndexedDB API

(2)ServiceWorker注册过程中出现异常,无法连上devtools,无法从devtools获取异常信息。

那么,ServiceWorker线程一般在什么情况下会被停止呢?

(1)ServiceWorker JS有任何异常,都会导致ServiceWorker线程退出。包括但不限于, JS文件存在语法错误, ServiceWorker安装失败/ 激活失败,ServiceWorker JS执行时出现未捕获的异常。

(2)ServiceWorker 功能事件处理完成,处于空闲状态,ServiceWorker线程会自动退出。

(3)ServiceWorker JS执行时间过长,ServiceWorker线程会自动退出。比如, ServiceWorker JS执行时间超过30秒,或Fetch请求在5分钟内还未完成。

(4)浏览器会周期性检查各个ServiceWorker线程是否可以退出, 一般在启动ServiceWorker线程30秒会检查一次,杀掉空闲超过30秒的ServiceWorker线程。

(5)为了方便开发者调试, Chromium进行了特殊处理, 在连上devtools之后,ServiceWorker线程不会退出。
参考:Keep a serviceworker alive when devtools is attached

异常类型

ServiceWorker线程在启动或执行代码的过程中,一般会有下面几类异常:

(1)ServiceWorker JS 存在语法错误

blink::WorkerThread::initialize

--> blink::WorkerScriptController::evaluate

--> blink::ExecutionContext::reportException  // 此次会抛出异常, Uncaught SyntaxError: Unexpected token function

--> blink::ExecutionContext::dispatchErrorEvent

--> ... ...

--> content::ServiceWorkerDispatcher::OnRegistrationError  // 引起注册失败

--> blink::ScriptPromiseResolver::reject

这种情况,一般在启动WorkerThread的时候,initialize初始化时,会调用ScriptController::evaluate去执行ServiceWorker的JS代码,检查到语法错误时,会引起ServiceWorker注册失败。

(2)ServiceWorker 安装/激活的代码存在异常

 举个例子,下面serviceworker.js的安装/激活函数中,直接调用了self.skipWaiting() / self.clients.claim(), 如果浏览器还未支持这些接口,会出现什么问题呢?

self.addEventListener('install', function(e) {
return self.skipWaiting();
});

self.addEventListener('activate', function(e) {
return self.clients.claim(); 
});

这种情况,一般会在执行安装/激活事件的JS函数时,直接报告异常。

content::ServiceWorkerScriptContext::OnActivateEvent

--> blink::ServiceWorkerGlobalScope::dispatchExtendableEvent

--> blink::V8ScriptRunner::callFunction

--> blink::ExecutionContext::reportException  // 此次会抛出异常, Uncaught TypeError: undefined is not a function

--> blink::ExecutionContext::dispatchErrorEvent

 --> ... ....

--> blink::WaitUntilObserver::didDispatchEvent

--> blink::ServiceWorkerGlobalScopeClientImpl::didHandleActivateEvent

--> content::EmbeddedWorkerContextClient::didHandleActivateEvent

--> content::ServiceWorkerScriptContext::DidHandleActivateEvent   // send IPC

--> content::ServiceWorkerVersion::OnActivateEventFinished  //Activate 失败,WebServiceWorkerEventResultRejected

--> content::ServiceWorkerRegistration::OnActivateEventFinished

--> content::ServiceWorkerVersion::Doom

--> content::ServiceWorkerVersion::StopWorker  // 引起ServiceWorker线程退出

注1:ScriptPromise本身会捕获异常,它仅仅返回Rejected/Fulfilled,并不会再将JS异常往上抛,很多时候前端仅仅能看到Promise Rejected了,但并不清楚是什么原因。

注2:WaitUntilObserver也一样,它也只返回Rejected/Fulfilled,并没有进一步将JS异常往上抛,很多时候前端仅仅能看到WaitUntil Rejected了,但并不清楚是什么原因。

(3)功能事件处理出错, 比如, Fetch ResponseWith出错

举个例子,下面serviceworker.js的fetch事件处理函数中,如果strategies.networkFallbackToCache执行出错了,会出现什么问题呢?

self.addEventListener("fetch", function(e) {
     return e.respondWith(strategies.networkFallbackToCache(e.request))
});

这种情况,respondWith会Rejected,但并不会抛异常, 表现为资源请求失败了,很可能造成页面白屏或者排版显示异常。

v8::internal::FunctionCallbackArguments::Call

--> blink::ScriptFunction::callCallback

--> blink::RespondWithObserver::ThenFunction::call

--> blink::RespondWithObserver::responseWasRejected  // respondWith会Rejected

 注: 这类问题也非常难跟进, 只能是一步一步的修改页面使用devtools等工具进行调试。

(4)普通的文档JS异常, 比如, Uncaught ReferenceError: require is not defined

blink::HTMLScriptRunner::execute

--> blink::ScriptLoader::executeScript

--> blink::ScriptController::executeScriptAndReturnValue

--> blink::V8ScriptRunner::runCompiledScript

--> v8::Script::Run

--> v8::internal::Execution::Call

--> v8::internal::Isolate::ReportPendingMessages

--> v8::internal::MessageHandler::ReportMessage

--> blink::ExecutionContext::reportException / blink::ExecutionContext::dispatchErrorEvent

(5)普通的JS函数调用异常, 比如, Uncaught ReferenceError: require is not defined

blink::ScriptController::callFunction

--> blink::V8ScriptRunner::callFunction

--> v8::Function::Call

--> v8::internal::Execution::Call

--> v8::internal::Isolate::ReportPendingMessages

--> v8::internal::MessageHandler::ReportMessage

--> blink::ExecutionContext::reportException / blink::ExecutionContext::dispatchErrorEvent

异常处理

从上面可以看到, ServiceWorker线程可能会出现各种各样的异常, 那么,我们有没有较统一的解决方案呢?坦诚的说,没有,需要具体问题具体分析。

一些可能的问题跟进思路:

(1)ServiceWorker注册/安装/激活失败

从浏览器开发的角度:

  • 思路一: 不让ServiceWorker线程退出,即使这些过程失败了也不退出。一种处理方式可以是,浏览器检测到文档与devtools连接,即不允许ServiceWorker线程退出。
  • 思路二:增加一些动态调试开关,在开关打开时,尽可能输出较完善的JS异常信息。比如,从上面的堆栈可以看到,很多异常都会走到ExecutionContext::reportException。
  • 思路三:完善ServiceWorker的错误处理流程,每个步骤出错都能输出清晰的日志。

从前端开发者的角度,一般的思路是尽可能连上devtools,如果没办法连上就逐步修改代码一步一步调试了,或者借助浏览器的一些调试日志进行分析。

(2)ServiceWorker功能函数执行异常

一般这种情况下,尽量想办法连上devtools的,在devtools上调试, 或者在业务代码增加一些调试日志。通常高版本的Chrome Devtools会有更加详细的调试信息,浏览器开发可以考虑增加更加完善的控制台或tracing日志。

 

参考文档

Service Workers: an Introduction

Limit Service Worker event execution time

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
4月前
|
缓存 JavaScript 前端开发
Web Workers与Service Workers:后台处理与离线缓存
Web Workers 和 Service Workers 是两种在Web开发中处理后台任务和离线缓存的重要技术。它们在工作原理和用途上有显著区别。
61 1
|
4月前
|
缓存 前端开发 JavaScript
JavaScript进阶 - Web Workers与Service Worker
【7月更文挑战第10天】在Web开发中,Web Workers和Service Worker提升性能。Workers运行后台任务,防止界面冻结。Web Workers处理计算密集型任务,Service Worker则缓存资源实现离线支持。常见问题包括通信故障、资源限制、注册错误及缓存更新。通过示例代码展示了两者用法,并强调生命周期管理和错误处理的重要性。善用这些技术,可构建高性能的Web应用。
82 0
|
11月前
|
Kubernetes 监控 Cloud Native
k8s 自身原理之 Service
k8s 自身原理之 Service
|
11月前
|
存储 缓存 前端开发
WorkBox 之底层逻辑Service Worker(一)
WorkBox 之底层逻辑Service Worker(一)
|
11月前
|
存储 Web App开发 缓存
WorkBox 之底层逻辑Service Worker(二)
WorkBox 之底层逻辑Service Worker(二)
167 0
|
缓存 JavaScript 前端开发
在项目中使用Service Worker 与 PWA
在项目中使用Service Worker 与 PWA
86 1
|
存储 缓存 前端开发
Service Worker实现离线缓存和推送通知
离线缓存和推送通知在提升网页的离线访问体验方面起着重要的作用。 离线缓存允许网页将所需的资源(如 HTML、CSS、JavaScript 文件、图像等)保存在用户设备的本地存储中。这意味着即使在没有网络连接的情况下,用户仍然可以访问网页的内容和功能。离线缓存不仅提供了更好的用户体验,而且还可以减轻服务器的负担,因为客户端可以直接通过本地缓存的资源进行加载,而无需每次都向服务器发出请求。
650 0
|
Android开发 Kotlin
Service以及多线程初步
Android里面更新UI必须在主线程中更新,子线程中不能更新UI,kotlin有其简化的开启线程方法
86 0
|
Web App开发 存储 缓存
Service Workers(PWA初体验)
在前端越来越重的这个时代,页面加载速度成为了一个重要的指标。对于这个问题,业界也有一些解决方案。 浏览器缓存、协议缓存、强缓存 懒加载(首屏) CDN 多域名突破下载并发限制。其实在两年前内部就对这块内容做过调研了。appCache方案?PWA方案?但是最后都没选择。 之前看代码,发现是 localstroage 存代码,如果有就拿 localstroage 去用。省去了这一部分加载的时间。上个同事离职了。当时的调研结果我也忘了。只能再开始新一轮的调研,我选择的是 PWA 方案。网上的资料很少。我希望我可以写一篇帮助下一个想使用 PWA 方案的人。
274 0
Service Workers(PWA初体验)