Service Worker 离线缓存

Service worker 本质上充当 Web 应用程序、浏览器与网络(可用时)之间的代理服务器。这个 API 旨在创建有效的离线体验,它会拦截网络请求并根据网络是否可用来采取适当的动作、更新来自服务器的的资源。它还提供入口以推送通知和访问后台同步 API。

Service worker 运行在 worker 上下文:因此它无法访问 DOM,相对于驱动应用的主 JavaScript 线程,它运行在其他线程中,所以不会造成阻塞。

出于安全考量,Service worker 只能由 HTTPS 或 localhost 使用。

注册

javascript 复制代码
if ('serviceWorker' in navigator) {
    navigator.serviceWorker
        .register('/service-worker.js', {
            scope: '/',
        })
        .then(function (registration) {
            // 注册成功
            console.log('ServiceWorker registered successful', registration.scope);
        })
        .catch(function (err) {
            // 注册失败
            console.log('ServiceWorker registration failed: ', err);
        });
}

scope 用来指定 ServiceWorker 的作用域,它的默认值是 ServiceWorker 脚本所在目录。

我们来看下控制台输出信息:

console 复制代码
ServiceWorker registered successful http://localhost:7016/

Service Worker 生命周期

  1. 下载
  2. 安装
  3. 激活
service-worker.js 复制代码
self.addEventListener('install', function (event) {
    console.log('install');
});

self.addEventListener('activate', function (event) {
    console.log('activate');
});

self.addEventListener('fetch', function (event) {
    console.log('fetch');
});

控制台打印:

刷新页面,控制台只剩下 fetch 事件,安装和激活只会在首次下载 Service Worker 时执行。

离线缓存

service-worker.js 复制代码
const addResourcesToCache = async (resources) => {
    const cache = await caches.open('v1');
    await cache.addAll(resources);
};

const putInCache = async (request, response) => {
    const cache = await caches.open('v1');
    await cache.put(request, response);
};

const cacheFirst = async ({ request, preloadResponsePromise, fallbackUrl }) => {
    // First try to get the resource from the cache
    const responseFromCache = await caches.match(request);
    if (responseFromCache) {
        return responseFromCache;
    }

    // Next try to use the preloaded response, if it's there
    const preloadResponse = await preloadResponsePromise;
    if (preloadResponse) {
        console.info('using preload response', preloadResponse);
        putInCache(request, preloadResponse.clone());
        return preloadResponse;
    }

    // Next try to get the resource from the network
    try {
        const responseFromNetwork = await fetch(request);
        // response may be used only once
        // we need to save clone to put one copy in cache
        // and serve second one
        putInCache(request, responseFromNetwork.clone());
        return responseFromNetwork;
    } catch (error) {
        const fallbackResponse = await caches.match(fallbackUrl);
        if (fallbackResponse) {
            return fallbackResponse;
        }
        // when even the fallback response is not available,
        // there is nothing we can do, but we must always
        // return a Response object
        return new Response('Network error happened', {
            status: 408,
            headers: { 'Content-Type': 'text/plain' },
        });
    }
};

const enableNavigationPreload = async () => {
    if (self.registration.navigationPreload) {
        // Enable navigation preloads!
        await self.registration.navigationPreload.enable();
    }
};

self.addEventListener('activate', (event) => {
    console.log('activate');
    event.waitUntil(enableNavigationPreload());
});

self.addEventListener('install', (event) => {
    console.log('install');
    event.waitUntil(
        addResourcesToCache([
            '/',
            '/index.html',
            '/index.css',
            'https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.12.1/polyfill.min.js',
            'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.16.2/highlight.min.js',
        ])
    );
});

self.addEventListener('fetch', (event) => {
    console.log('fetch', event.request);
    event.respondWith(
        cacheFirst({
            request: event.request,
            preloadResponsePromise: event.preloadResponse,
            fallbackUrl: '/fallback',
        })
    );
});

使用 CacheStorage 来进行缓存。

首次访问后,刷新页面,查看 Network, 发现已经改为从 ServiceWorker 加载缓存的资源文件

再来看下控制台

我们通过监听 fetch 事件进行请求拦截,它会拦截所有的请求。这里也在 fetch 监听函数中把请求给缓存了下来,但是只能缓存 Get 请求。

点击发送请求,看到请求首次发送之后,会进行缓存,再次点击会从 ServiceWorker 中获取。

我们断开网络,关闭浏览器后重新打开,发现页面仍能正常访问

点击发送请求,返回的是 ServiceWorker 中缓存的结果。

切换至 Application,看到缓存存放在 CacheStorage 中,不会随页面的关闭而销毁。

相关推荐
Jonathan Star3 小时前
沉浸式雨天海岸:用A-Frame打造WebXR互动场景
前端·javascript
工业甲酰苯胺4 小时前
实现 json path 来评估函数式解析器的损耗
java·前端·json
老前端的功夫4 小时前
Web应用的永生之术:PWA落地与实践深度指南
java·开发语言·前端·javascript·css·node.js
LilySesy4 小时前
ABAP+WHERE字段长度不一致报错解决
java·前端·javascript·bug·sap·abap·alv
Wang's Blog5 小时前
前端FAQ: Vue 3 与 Vue 2 相⽐有哪些重要的改进?
前端·javascript·vue.js
再希5 小时前
React+Tailwind CSS+Shadcn UI
前端·react.js·ui
用户47949283569156 小时前
JavaScript 的 NaN !== NaN 之谜:从 CPU 指令到 IEEE 754 标准的完整解密
前端·javascript
群联云防护小杜6 小时前
国产化环境下 Web 应用如何满足等保 2.0?从 Nginx 配置到 AI 防护实战
运维·前端·nginx
醉方休6 小时前
Web3.js 全面解析
前端·javascript·electron
前端开发爱好者7 小时前
前端新玩具:Vike 发布!
前端·javascript