Cache Storage API is the core API of Service Worker, used to manage caching of request/response pairs, enabling offline access and performance optimization.
Basic Concepts
Cache Storage API provides a database-like interface to store and retrieve network requests and responses:
javascript// Cache Storage is the caches global object console.log(caches); // CacheStorage object // Main methods caches.open(cacheName) // Open/create cache caches.match(request) // Match request across caches caches.keys() // Get all cache names caches.delete(cacheName) // Delete specified cache
Core Methods Explained
1. Open and Create Cache
javascript// Open cache (creates if doesn't exist) async function openCache() { const cache = await caches.open('my-cache-v1'); console.log('Cache opened:', cache); return cache; } // Use version numbers for cache management const CACHE_VERSION = 'v2'; const CACHE_NAME = `app-cache-${CACHE_VERSION}`;
2. Add Resources to Cache
javascript// Method 1: use add() - fetch and cache single request async function addToCache(url) { const cache = await caches.open('my-cache'); await cache.add(url); // Automatically fetch and cache console.log(`Cached: ${url}`); } // Method 2: use addAll() - batch cache async function addMultipleToCache(urls) { const cache = await caches.open('my-cache'); await cache.addAll(urls); console.log(`Batch cached ${urls.length} resources`); } // Usage example const urlsToCache = [ '/', '/index.html', '/styles.css', '/app.js', '/images/logo.png' ]; addMultipleToCache(urlsToCache); // Method 3: use put() - manually store request/response pair async function putInCache(request, response) { const cache = await caches.open('my-cache'); await cache.put(request, response); } // Example: cache custom response const request = new Request('/custom-data'); const response = new Response(JSON.stringify({ data: 'value' }), { headers: { 'Content-Type': 'application/json' } }); putInCache(request, response);
3. Retrieve from Cache
javascript// Method 1: match() - match single request async function getFromCache(url) { const cache = await caches.open('my-cache'); const response = await cache.match(url); if (response) { console.log('Cache hit:', url); return response; } console.log('Cache miss:', url); return null; } // Method 2: match across all caches caches.match('/api/data').then(response => { if (response) { // Found match in all caches return response; } }); // Method 3: matchAll() - get all matches async function getAllMatches(url) { const cache = await caches.open('my-cache'); const responses = await cache.matchAll(url); return responses; }
4. Delete Resources from Cache
javascript// Delete specific request async function deleteFromCache(url) { const cache = await caches.open('my-cache'); const deleted = await cache.delete(url); console.log(deleted ? 'Delete successful' : 'Resource does not exist'); } // Delete entire cache async function deleteCache(cacheName) { const deleted = await caches.delete(cacheName); console.log(deleted ? 'Cache deleted' : 'Cache does not exist'); } // Clean old caches (commonly used in activate event) async function cleanOldCaches(currentCacheName) { const cacheNames = await caches.keys(); const oldCaches = cacheNames.filter(name => name !== currentCacheName); await Promise.all( oldCaches.map(name => caches.delete(name)) ); console.log(`Cleaned ${oldCaches.length} old caches`); }
5. View Cache Contents
javascript// List all caches async function listAllCaches() { const cacheNames = await caches.keys(); console.log('All caches:', cacheNames); return cacheNames; } // View all requests in specific cache async function listCacheContents(cacheName) { const cache = await caches.open(cacheName); const requests = await cache.keys(); console.log(`Cache "${cacheName}" contents:`); requests.forEach(request => { console.log(' -', request.url); }); return requests; }
Practical Application Scenarios
Scenario 1: Precache Static Resources
javascript// sw.js const STATIC_CACHE = 'static-v1'; const STATIC_ASSETS = [ '/', '/index.html', '/styles.css', '/app.js', '/manifest.json', '/icons/icon-192x192.png' ]; self.addEventListener('install', event => { event.waitUntil( caches.open(STATIC_CACHE) .then(cache => cache.addAll(STATIC_ASSETS)) .then(() => self.skipWaiting()) ); });
Scenario 2: Dynamic Cache Runtime Resources
javascript// sw.js const DYNAMIC_CACHE = 'dynamic-v1'; self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request).then(response => { // Return cache or fetch from network return response || fetch(event.request).then(fetchResponse => { // Dynamically cache new resources return caches.open(DYNAMIC_CACHE).then(cache => { cache.put(event.request, fetchResponse.clone()); return fetchResponse; }); }); }) ); });
Scenario 3: Cache API Responses
javascript// sw.js const API_CACHE = 'api-v1'; const API_MAX_AGE = 5 * 60 * 1000; // 5 minutes self.addEventListener('fetch', event => { if (event.request.url.includes('/api/')) { event.respondWith( caches.open(API_CACHE).then(cache => { return cache.match(event.request).then(cachedResponse => { // Check if cache is expired if (cachedResponse) { const cachedTime = new Date( cachedResponse.headers.get('sw-cached-time') ).getTime(); if (Date.now() - cachedTime < API_MAX_AGE) { return cachedResponse; } } // Fetch from network and cache return fetch(event.request).then(networkResponse => { const clonedResponse = networkResponse.clone(); // Add custom cache time header const headers = new Headers(clonedResponse.headers); headers.set('sw-cached-time', new Date().toISOString()); const modifiedResponse = new Response( clonedResponse.body, { ...clonedResponse, headers } ); cache.put(event.request, modifiedResponse); return networkResponse; }); }); }) ); } });
Scenario 4: Cache Cleanup Strategy
javascript// sw.js const CACHE_MAX_ITEMS = 50; // Maximum cache items async function trimCache(cacheName, maxItems) { const cache = await caches.open(cacheName); const requests = await cache.keys(); if (requests.length > maxItems) { // Delete oldest cache const toDelete = requests.slice(0, requests.length - maxItems); await Promise.all( toDelete.map(request => cache.delete(request)) ); console.log(`Cleaned ${toDelete.length} old cache items`); } } // Periodic cleanup self.addEventListener('activate', event => { event.waitUntil( trimCache('dynamic-v1', CACHE_MAX_ITEMS) ); });
Important Notes
1. Response Can Only Be Used Once
javascript// ❌ Wrong: Response can only be read once const response = await fetch('/api/data'); await cache.put(request, response); // Store to cache return response; // Already consumed, cannot return // ✅ Correct: use clone() const response = await fetch('/api/data'); await cache.put(request, response.clone()); // Store copy return response; // Return original response
2. Storage Limits
javascript// Check storage quota async function checkStorageQuota() { if ('storage' in navigator && 'estimate' in navigator.storage) { const estimate = await navigator.storage.estimate(); console.log(`Used: ${estimate.usage} bytes`); console.log(`Quota: ${estimate.quota} bytes`); console.log(`Usage: ${(estimate.usage / estimate.quota * 100).toFixed(2)}%`); } }
3. Cache Key Matching Rules
javascript// URL must match exactly (including query parameters) await cache.match('/api/data'); // ✅ matches /api/data await cache.match('/api/data?id=1'); // ❌ doesn't match /api/data // Use ignoreSearch option to ignore query parameters await cache.match('/api/data?id=1', { ignoreSearch: true // Ignore query parameters }); // Other matching options await cache.match(request, { ignoreSearch: true, // Ignore query parameters ignoreMethod: true, // Ignore HTTP method ignoreVary: true // Ignore Vary header });
Best Practices
- Version Control: Use version numbers for cache management, easy to update
- Regular Cleanup: Clean expired caches in activate event
- Storage Limits: Be aware of browser storage quota limits
- Error Handling: Add appropriate error handling and fallback solutions
- Performance Optimization: Avoid caching too much data, clean regularly