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日志并进行多维度分析。
目录
相关文章
|
9天前
|
存储 缓存 安全
在 Service Worker 中配置缓存策略
Service Worker 是一种可编程的网络代理,允许开发者控制网页如何加载资源。通过在 Service Worker 中配置缓存策略,可以优化应用性能,减少加载时间,提升用户体验。此策略涉及缓存的存储、更新和检索机制。
|
4月前
|
缓存 JavaScript 前端开发
Web Workers与Service Workers:后台处理与离线缓存
Web Workers 和 Service Workers 是两种在Web开发中处理后台任务和离线缓存的重要技术。它们在工作原理和用途上有显著区别。
63 1
|
4月前
|
缓存 前端开发 JavaScript
JavaScript进阶 - Web Workers与Service Worker
【7月更文挑战第10天】在Web开发中,Web Workers和Service Worker提升性能。Workers运行后台任务,防止界面冻结。Web Workers处理计算密集型任务,Service Worker则缓存资源实现离线支持。常见问题包括通信故障、资源限制、注册错误及缓存更新。通过示例代码展示了两者用法,并强调生命周期管理和错误处理的重要性。善用这些技术,可构建高性能的Web应用。
91 0
|
11月前
|
存储 缓存 前端开发
WorkBox 之底层逻辑Service Worker(一)
WorkBox 之底层逻辑Service Worker(一)
105 0
|
11月前
|
存储 Web App开发 缓存
WorkBox 之底层逻辑Service Worker(二)
WorkBox 之底层逻辑Service Worker(二)
170 0
|
缓存 JavaScript 前端开发
在项目中使用Service Worker 与 PWA
在项目中使用Service Worker 与 PWA
88 1
|
Android开发 Kotlin
Service以及多线程初步
Android里面更新UI必须在主线程中更新,子线程中不能更新UI,kotlin有其简化的开启线程方法
87 0
|
Web App开发 存储 缓存
Service Workers(PWA初体验)
在前端越来越重的这个时代,页面加载速度成为了一个重要的指标。对于这个问题,业界也有一些解决方案。 浏览器缓存、协议缓存、强缓存 懒加载(首屏) CDN 多域名突破下载并发限制。其实在两年前内部就对这块内容做过调研了。appCache方案?PWA方案?但是最后都没选择。 之前看代码,发现是 localstroage 存代码,如果有就拿 localstroage 去用。省去了这一部分加载的时间。上个同事离职了。当时的调研结果我也忘了。只能再开始新一轮的调研,我选择的是 PWA 方案。网上的资料很少。我希望我可以写一篇帮助下一个想使用 PWA 方案的人。
278 0
Service Workers(PWA初体验)
|
存储 缓存 JSON
[译] JS 中 service workers 的简介
Service workers是Progressive Web Apps的核心部分,允许缓存资源和Web推送通知等,以创建良好的离线体验。它们充当Web应用程序,浏览器和网络之间的代理,允许开发人员拦截和缓存网络请求,并基于网络的可用性采取适当的操作。
|
Web App开发 缓存 JavaScript
PWA系列 - Service Workers 启动性能
ServiceWorker线程启动有较大的成本,它会直接影响到PWA页面的实际效果。本文详细分析ServiceWorker的启动耗时和可能的解决思路。
2723 0