嘿,各位空中飞人们,经常出差是不是最怕落地后没信号,想查个天气都抓瞎?今天咱们就来聊聊怎么用 Service Worker 这玩意儿,给你的天气预报 PWA(Progressive Web App)装上离线“外挂”,保证没网也能心里有数!
啥是Service Worker?它凭啥这么牛?
简单来说,Service Worker 就是一个跑在你浏览器背后的“小弟”,它能拦截你的网络请求,然后决定是从缓存里拿数据,还是去网络上请求最新的。这就厉害了,相当于给你的 PWA 提前准备好了“备胎”,没网的时候直接用“备胎”顶上,体验瞬间提升 N 个档次!
想象一下这个场景:
你刚下飞机,打开天气预报 PWA,就算没 Wi-Fi,也能看到昨天查过的北京天气,心里顿时踏实了不少,知道今天该穿啥。这就是 Service Worker 的功劳!
咱们的目标:打造一个靠谱的离线天气预报 PWA
- 离线可用: 这是最基本的要求,没网也能看最近浏览过的城市天气。
- 自动更新: 一旦网络恢复,Service Worker 就能自动去更新数据,保证你看到的是最新的天气情况。
- 用户提示: 离线状态下,要明确告诉用户“现在是离线模式”,避免用户误以为数据是实时的。
- 数据过期处理: 天气数据有时效性,要考虑数据过期的问题,给用户一个合理的提示。
撸起袖子,开干!Service Worker实战教程
1. 注册你的 Service Worker
首先,在你的主 JavaScript 文件里,加上这段代码:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(function(registration) {
console.log('Service Worker 注册成功,作用域:', registration.scope);
})
.catch(function(error) {
console.log('Service Worker 注册失败:', error);
});
}
这段代码会检查浏览器是否支持 Service Worker,如果支持,就注册你的 service-worker.js
文件。这个文件就是 Service Worker 的核心代码。
2. 编写 Service Worker 脚本(service-worker.js)
这个文件是重头戏,咱们一步一步来:
2.1 定义缓存名称和要缓存的资源
const CACHE_NAME = 'weather-app-cache-v1';
const urlsToCache = [
'/',
'/index.html',
'/style.css',
'/script.js',
'/images/icon.png',
// 这里可以添加你 PWA 需要缓存的其他资源,比如字体文件、图片等等
];
CACHE_NAME
:缓存的名称,每次更新缓存时,可以修改这个名称,浏览器会自动更新缓存。urlsToCache
:一个数组,包含了你需要缓存的静态资源,比如 HTML、CSS、JavaScript、图片等等。
2.2 监听 install 事件
当 Service Worker 注册成功后,会触发 install
事件。在这个事件里,咱们可以预先缓存一些资源:
self.addEventListener('install', function(event) {
// 延迟 install 事件,直到缓存完成
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('已打开缓存');
return cache.addAll(urlsToCache);
})
);
});
这段代码会打开一个名为 weather-app-cache-v1
的缓存,然后把 urlsToCache
里的所有资源都放进去。这样,你的 PWA 在第一次加载时,就会把这些资源缓存起来,下次再访问时,就可以直接从缓存里读取,速度飞快!
2.3 监听 fetch 事件
当你的 PWA 发起网络请求时,会触发 fetch
事件。在这个事件里,咱们可以决定是从缓存里拿数据,还是去网络上请求最新的:
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// 缓存命中
if (response) {
return response;
}
// 缓存未命中,去网络请求
return fetch(event.request).then(
function(response) {
// 检查是否收到了有效的响应
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// 克隆一份响应,因为 response body 是 stream,只能读取一次
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
这段代码首先会尝试从缓存里查找对应的资源,如果找到了,就直接返回缓存里的数据。如果没找到,就去网络上请求,然后把请求到的数据也放进缓存里,下次再访问时,就可以直接从缓存里读取了。
注意:
- 这段代码只缓存了 GET 请求,因为 POST 请求通常是用来提交数据的,不适合缓存。
- 在缓存网络请求的响应之前,要先克隆一份,因为 response body 是 stream,只能读取一次。
2.4 监听 activate 事件
当 Service Worker 更新时,会触发 activate
事件。在这个事件里,咱们可以清理旧的缓存:
self.addEventListener('activate', function(event) {
var cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(cacheName) {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});
这段代码会获取所有的缓存名称,然后删除不在 cacheWhitelist
里的缓存。cacheWhitelist
包含了你想要保留的缓存名称,比如当前的缓存名称 CACHE_NAME
。这样,你就可以在更新 Service Worker 时,自动清理旧的缓存,避免缓存污染。
3. 获取天气数据并缓存
3.1 修改 fetch 事件的处理逻辑
为了实现离线查看天气的功能,我们需要修改 fetch
事件的处理逻辑,只缓存天气 API 的请求结果。
假设你的天气 API 的 URL 是 https://api.example.com/weather?city=北京
,你可以这样修改 fetch
事件的处理逻辑:
self.addEventListener('fetch', function(event) {
// 只处理天气 API 的请求
if (event.request.url.startsWith('https://api.example.com/weather')) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// 缓存命中
if (response) {
return response;
}
// 缓存未命中,去网络请求
return fetch(event.request).then(
function(response) {
// 检查是否收到了有效的响应
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// 克隆一份响应
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
} else {
// 其他请求,直接去网络请求
event.respondWith(fetch(event.request));
}
});
3.2 在前端代码中,把天气数据存储到 localStorage
为了在离线状态下显示天气信息,我们需要把最近浏览过的城市天气数据存储到 localStorage
里。当 Service Worker 拦截到天气 API 的请求时,可以先从 localStorage
里读取数据,如果 localStorage
里没有数据,再去网络上请求。
// 获取天气数据
function getWeather(city) {
fetch(`https://api.example.com/weather?city=${city}`)
.then(response => response.json())
.then(data => {
// 把天气数据存储到 localStorage
localStorage.setItem(city, JSON.stringify(data));
// 显示天气数据
displayWeather(data);
});
}
// 显示天气数据
function displayWeather(data) {
// ...
}
3.3 修改 fetch 事件的处理逻辑,从 localStorage 读取数据
self.addEventListener('fetch', function(event) {
// 只处理天气 API 的请求
if (event.request.url.startsWith('https://api.example.com/weather')) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// 缓存命中
if (response) {
return response;
}
// 缓存未命中,从 localStorage 读取数据
const city = new URL(event.request.url).searchParams.get('city');
const weatherData = localStorage.getItem(city);
if (weatherData) {
// 把 localStorage 里的数据转换成 Response 对象
const response = new Response(weatherData, {
headers: {
'Content-Type': 'application/json'
}
});
return response;
}
// localStorage 里也没有数据,去网络请求
return fetch(event.request).then(
function(response) {
// 检查是否收到了有效的响应
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// 克隆一份响应
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
} else {
// 其他请求,直接去网络请求
event.respondWith(fetch(event.request));
}
});
4. 用户体验优化:离线提示和数据过期处理
4.1 离线提示
当用户在离线状态下访问你的 PWA 时,你需要明确地告诉用户“现在是离线模式”,避免用户误以为数据是实时的。你可以在页面上添加一个提示信息,或者使用一个全局的 JavaScript 变量来表示当前是否处于离线状态。
// 监听 online 和 offline 事件
window.addEventListener('online', updateOnlineStatus);
window.addEventListener('offline', updateOnlineStatus);
function updateOnlineStatus() {
if (navigator.onLine) {
// Online
console.log('Online');
// 移除离线提示
document.getElementById('offline-message').style.display = 'none';
} else {
// Offline
console.log('Offline');
// 显示离线提示
document.getElementById('offline-message').style.display = 'block';
}
}
4.2 数据过期处理
天气数据有时效性,你需要考虑数据过期的问题,给用户一个合理的提示。你可以在 localStorage
里存储天气数据时,也存储一个时间戳,表示数据的过期时间。当 Service Worker 从 localStorage
里读取数据时,可以检查数据是否过期,如果过期了,就显示一个提示信息,告诉用户数据可能不是最新的。
// 获取天气数据
function getWeather(city) {
fetch(`https://api.example.com/weather?city=${city}`)
.then(response => response.json())
.then(data => {
// 存储数据和时间戳
const weatherData = {
data: data,
timestamp: Date.now()
};
localStorage.setItem(city, JSON.stringify(weatherData));
// 显示天气数据
displayWeather(data);
});
}
// 修改 fetch 事件的处理逻辑,检查数据是否过期
self.addEventListener('fetch', function(event) {
// 只处理天气 API 的请求
if (event.request.url.startsWith('https://api.example.com/weather')) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// 缓存命中
if (response) {
return response;
}
// 缓存未命中,从 localStorage 读取数据
const city = new URL(event.request.url).searchParams.get('city');
const weatherDataString = localStorage.getItem(city);
if (weatherDataString) {
const weatherData = JSON.parse(weatherDataString);
// 检查数据是否过期 (假设过期时间为 1 小时)
if (Date.now() - weatherData.timestamp < 60 * 60 * 1000) {
// 数据未过期,返回数据
const response = new Response(JSON.stringify(weatherData.data), {
headers: {
'Content-Type': 'application/json'
}
});
return response;
} else {
// 数据已过期,显示提示信息
return new Response(JSON.stringify({
error: '天气数据已过期,请联网更新'
}), {
headers: {
'Content-Type': 'application/json'
}
});
}
}
// localStorage 里也没有数据,去网络请求
return fetch(event.request).then(
function(response) {
// 检查是否收到了有效的响应
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// 克隆一份响应
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
} else {
// 其他请求,直接去网络请求
event.respondWith(fetch(event.request));
}
});
总结
通过 Service Worker,我们可以轻松地给天气预报 PWA 加上离线功能,让用户在没网的情况下也能查看最近浏览过的城市天气。同时,我们还可以通过离线提示和数据过期处理,进一步提升用户体验。赶紧动手试试吧,让你的 PWA 更加强大!
温馨提示:
- Service Worker 的调试比较麻烦,可以使用 Chrome 的 DevTools 来进行调试。
- Service Worker 的更新需要一些时间,可能需要手动刷新页面才能看到最新的效果。
希望这篇文章能帮助你打造一个靠谱的离线天气预报 PWA,祝你旅途愉快!