JS 中 service workers 的简介

2,189 阅读5分钟

Service workersProgressive Web Apps的核心部分,允许缓存资源和Web推送通知等,以创建良好的离线体验。它们充当Web应用程序,浏览器和网络之间的代理,允许开发人员拦截和缓存网络请求,并基于网络的可用性采取适当的操作。

一个service worker在单独的线程上运行,因此它是非阻塞的。这也意味着它无法访问主JavaScript线程中可用的DOM和其他API,比如cookie,XHR,Web存储API(本地存储和会话存储)等。由于它们被设计为完全异步,因此它们重度依赖promise来等待网络请求的响应。

出于安全考虑,service workers仅使用HTTPS运行,且不能在隐私浏览模式下使用。但是,在发出本地请求的时候,你不需要安全连接(这足以进行测试)。

浏览器支持

Service Workers是一种相对较新的API,仅受现代浏览器的支持。因此,我们首先需要检查浏览器是否支持该API:

if('serviceWorker' in navigator) {
    // Supported 😍
} else {
    // Not supported 😥
}

Service Worker 注册

在我们开始缓存资源或拦截网络请求之前,我们必须在浏览器中安装service worker。由于service worker本质上是一个JavaScript文件,因此可以通过指定文件的路径来注册它。该文件必须可以通过网络访问,并且只应包含service worker代码。

你应该等待页面加载完成,然后将service worker文件路径传给navigator.serviceWorker.register()方法:

window.addEventListener('load', () => {
    if ('serviceWorker' in navigator) {
        // register service worker
        navigator.serviceWorker.register('/sw-worker.js').then(
            () => {
                console.log('SW registration succesful 😍');
            },
            err => {
                console.error('SW registration failed 😠', err)
            });
    } else {
        // Not supported 😥
    }
});

每次页面加载时都可以运行上面的代码,没有任何问题;浏览器将决定是否已经安装service worker并相应地处理它。

Service Worker 生命周期

注册生命周期包括三个步骤:

  1. 下载
  2. 安装
  3. 激活

当用户首次访问您的网站时,会立即下载service worker文件并尝试安装。如果安装成功,则激活service worker。在用户访问另一个页面后刷新当前页面之前,service worker文件中的任何功能都不可用。

浏览器事件

一旦service worker被安装并激活了,它就可以开始拦截网络请求和缓存资源。这可以通过监听service worker文件中浏览器发出的事件来完成。浏览器发出以下事件:

  • install 当安装service worker程序时将发出install
  • activate 成功注册和安装service worker程序后将发送active。在安装新版本之前,此事件可用于删除过期的缓存资源。
  • fetch 只要网页请求网络资源,就会发出fetch。资源可以是任何东西:新的HTML文档,图像,JSON API,样式表或者JavaScript文件,以及远程位置上可用的任何内容。
  • push 当收到新的推送通知时,push由Push API发送。你可以使用此事件向用户显示通知
  • sync 当浏览器在连接丢失后检测到网络可用性时,将掉哟个sync

提供缓存资源

我们可以在安装service worker时监听install事件,以缓存当我们离开网络时需要为网页提供服务的特定资源:

const CACHE_NAME = 'site-name-cache';

self.addEventListener('install', event => {
    event.waitUntil(
        caches
            .open(CACHE_NAME)
            .then(cache =>
                cache.addAll([
                    'favicon.ico',
                    'projects.json',
                    'style.css',
                    'index.js',
                    'https://fonts.googleapis.com/css?family=Open+Sans:400,700'
                ])
            )
    );
});

上面的例子中,代码使用Cache API将资源存储在名为site-name-cache的缓存中。

self关键字是一个只读的全局属性,service workers使用它来访问自己。

现在让我们监听一个fetch事件来检查所请求的资源是否已经存储在缓存中,如果找到则将其返回:

// ...
self.addEventListener('fetch', event => {
    event.respondWith(
        caches.match(event.request).then(response => {
            if (response) {
                //found cached resource
                return response;
            }
            return fetch(event.request);
        })
    );
});

我们查找请求属性标识的资源缓存条目,如果没有找到,我们会发送获取请求。如果你也想缓存新的请求,可以通过处理获取请求的响应然后将其添加到缓存来完成,如下所示:

// ...
self.addEventListener('fetch', event => {
    event.respondWith(
        caches.match(event.request).then(response => {
            if (response) {
                //found cached resource
                return response;
            }

            // get resource and add it to cache
            return fetch(event.request)
                .then(response => {
                    // check if the response is valid
                    if (!response.ok) {
                        return response;
                    }

                    // clone the response
                    const newResponse = response.clone();

                    // add it to cache
                    caches.open(CACHE_NAME)
                        .then(cache =>
                            cache.put(event.request, newResponse)
                        );

                    // return response
                    return response;
                });
        })
    );
});

Service Worker 更新

安装service worker程序后,它将继续运行,直到用户将其删除或者更新为止。要更新service worker,你需要做的就是在服务器上上传新版本的service worker文件。当用户访问你的站点时,浏览器将自动检测文件更改(即使只有一个字节更改就足够了),并安装新版本。

就像第一次安装一样,只有当用户导航到另一个页面或刷新当前页面时,新的service worker的功能才能使用。

我们可以做的事情就是监听activate事件,并删除旧的缓存资源。以下代码通过遍历所有缓存并删除与缓存名称匹配的缓存来完成此操作:

// ...
self.addEventListener('activate', event => {
    event.waitUntil(
        caches.keys().then(keys => {
            return Promise.all(
                keys.map(cache => {
                    if (cache === CACHE_NAME) {
                        return caches.delete(cache);
                    }
                })
            );
        })
    );
});

以上就是service workers的简介了。如果你想了解更多,移步SerciceWorker Cookbook -- 这里有一系列现代网站中使用service worker的实用例子。

参考和后话

更多的内容,请戳我的博客进行了解,能留个star就更好了💨