Service Worker

Service Worker_第1张图片

基本特性

  • 不能直接访问/操作 DOM(只能使用特定的 API:Promise、Fetch API、Cache API)
  • 需要时直接唤醒,不需要时自动休眠
  • 离线缓存内容开发者可控
  • 一旦被安装则永远存活,除非手动卸载
  • 必须在 HTTPS 环境下工作(本地环境 localhost 除外)
  • 广泛使用了 Promise

使用

  • 生命周期相关知识
    • install 阶段缓存文件列表
    • activate 阶段清理缓存
    • fetch 拦截请求

Service Worker_第2张图片

注册

main.html 下

// 同域下允许注册多个不同 scope 的 service worker
if ('serviceWorker' in navigator) {
    // 缩短白屏时间
    window.addEventListener('load', function () {
        // scope:指定访问的域
        navigator.serviceWorker.register('/static/sw-demo.js', { scope: '/static' })
            .then(registration => { // 注册成功
                console.log(registration.scope);
            })
            .catch(error => { // 注册失败

            })
    })
}

Service Worker_第3张图片
Service Worker_第4张图片

安装

sw-demo.js 下

const myCache = {
    NAME: 'my-cache-v0',
    data: [
        '/',
        '/test.js',
        'test.css'
    ]
}

this.addEventListener('install', function (event) {
    // 控制安装阶段流程,只有里面的 resolve 了,安装阶段才结束
    event.waitUntill(
        caches.open(myCache.NAME) // 开辟缓存区
            .then(cache => {
                // 缓存指定文件列表(注意文件缓存列表过长,会增加缓存失败的概率)
                return cache.addAll(myCache.data)
            })
    )
})
激活

sw-demo.js 下

this.addEventListener('activate', function (event) {
    // 控制激活阶段流程,只有里面的 resolve 了,激活阶段才结束
    event.waitUntill(
        Promise.all([
            // 跳过刷新页面的过程,让其立即具备所有的正常功能
            this.clients.claim(),
            caches.then((cacheList) => {
                return Promise.all(
                    cacheList.map(cacheName => {
                        // 监测缓存如果不匹配则丢弃缓存
                        if (cacheName !== myCache.NAME) {
                            return caches.delete(cacheName)
                        }
                    })
                )
            })
        ])
    )
})

案例

main.js

if ('serviceWorker' in navigator) {
    window.addEventListener('load', function (event) {
        navigator.serviceWorker.register('/sw.js', {
            scope: '/'
        })
            .then(function (registeration) {
                console.log('Service worker register success with scope ' + registeration.scope);
            });
    });

    navigator.serviceWorker.oncontrollerchange = function (event) {
        ui.showToast('页面已更新', 'info');
    };

    // 如果用户处于断网状态进入页面,用户可能无法感知内容是过期,需要提示用户断网了,并在重新连接后告诉用户
    if (!window.navigator.onLine) {
        ui.showToast('网络断开,内容可能已过期', 'info');
        window.addEventListener('online', function () {
            ui.showToast('已连接网络', 'info');
        });
    }
}

sw.js

const my_cache = {
    CACHE_NAME: 'cache_v_0',
    CACHE_URLS: [
        '/',
        '/js/ui.js',
        'js/render.js',
        '/js/main.js',
        '/api/movies',
        '/css/main.css',
        '/img/logo.png'
    ]
}

// 下载新的缓存
self.addEventListener('install', function (event) {
    event.waitUntil(
        precache().then(self.skipWaiting)
    );
});

// 删除就的缓存
self.addEventListener('activate', function (event) {
    event.waitUntil(
        Promise.all([
            self.clients.claim(),
            clearStaleCache()
        ])
    );
});

self.addEventListener('fetch', function (event) {
    // 只对同源的资源走 sw,cdn 上的资源利用 http 缓存策略
    if (new URL(event.request.url).origin !== self.origin) {
        return;
    }

    if (event.request.url.includes('/api/movies')) {
        event.respondWith(
            fetchAndCache(event.request)
                .catch(function () {
                    return caches.match(event.request);
                })
        );
        return;
    }

    event.respondWith(
        fetch(event.request).catch(function () {
            return caches.match(event.request);
        })
    );
});

/**
 * 缓存到 cacheStorage 里
 *
 * @param {Request} req 请求对象
 * @param {Response} res 响应对象
 */
function saveToCache(req, res) {
    return caches
        .open(my_cache.CACHE_NAME)
        .then(cache => cache.put(req, res));
}

/**
 * 预缓存
 *
 * @return {Promise} 缓存成功的promise
 */
function precache() {
    return caches.open(my_cache.CACHE_NAME).then(function (cache) {
        return cache.addAll(my_cache.CACHE_URLS);
    });
}

/**
 * 清除过期的 cache
 *
 * @return {Promise} promise
 */
function clearStaleCache() {
    return caches.keys().then(keys => {
        keys.forEach(key => {
            if (my_cache.CACHE_NAME !== key) {
                caches.delete(key);
            }
        });
    });
}

/**
 * 请求并缓存内容
 *
 * @param {Request} req request
 * @return {Promise}
 */
function fetchAndCache(req) {
    return fetch(req)
        .then(function (res) {
            saveToCache(req, res.clone());
            return res;
        });
}

成熟缓存工具

  • sw-precache 预缓存
  • sw-toolbox 动态缓存
  • workbox 集大成者

你可能感兴趣的:(javascript)