Mestre avanserte Fetch API-teknikker: avskjær forespørsler og implementer respons-caching for optimal ytelse og globale applikasjoner.
Avansert Fetch API: Avskjæring av forespørsler og respons-caching
Fetch API har blitt standarden for å gjøre nettverksforespørsler i moderne JavaScript. Mens grunnleggende bruk er enkel, krever det å utnytte dets fulle potensial en forståelse for avanserte teknikker som avskjæring av forespørsler og respons-caching. Denne artikkelen vil utforske disse konseptene i detalj, med praktiske eksempler og beste praksis for å bygge høytytende, globalt tilgjengelige webapplikasjoner.
Forstå Fetch API
Fetch API tilbyr et kraftig og fleksibelt grensesnitt for å hente ressurser over nettverket. Det bruker Promises, noe som gjør asynkrone operasjoner enklere å håndtere og resonnere rundt. Før vi dykker ned i avanserte emner, la oss kort gjennomgå det grunnleggende:
Grunnleggende Fetch-bruk
En enkel Fetch-forespørsel ser slik ut:
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log('Data:', data);
})
.catch(error => {
console.error('Fetch error:', error);
});
Denne koden henter data fra den angitte URL-en, sjekker for HTTP-feil, parser responsen som JSON og logger dataene til konsollen. Feilhåndtering er avgjørende for å sikre en robust applikasjon.
Avskjæring av forespørsler
Avskjæring av forespørsler innebærer å modifisere eller observere nettverksforespørsler før de sendes til serveren. Dette kan være nyttig for ulike formål, inkludert:
- Legge til autentiserings-headere
- Transformere forespørselsdata
- Logge forespørsler for feilsøking
- Mocke API-responser under utvikling
Avskjæring av forespørsler oppnås vanligvis ved hjelp av en Service Worker, som fungerer som en proxy mellom webapplikasjonen og nettverket.
Service Workers: Grunnlaget for avskjæring
En Service Worker er en JavaScript-fil som kjører i bakgrunnen, atskilt fra nettleserens hovedtråd. Den kan avskjære nettverksforespørsler, cache responser og tilby offline-funksjonalitet. For å bruke en Service Worker, må du først registrere den:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(registration => {
console.log('Service Worker registered with scope:', registration.scope);
})
.catch(error => {
console.error('Service Worker registration failed:', error);
});
}
Denne koden sjekker om nettleseren støtter Service Workers og registrerer service-worker.js
-filen. Omfanget (scope) definerer hvilke URL-er Service Worker-en vil kontrollere.
Implementering av avskjæring av forespørsler
Inne i service-worker.js
-filen kan du avskjære forespørsler ved hjelp av fetch
-hendelsen:
self.addEventListener('fetch', event => {
// Intercept all fetch requests
event.respondWith(
new Promise(resolve => {
// Clone the request to avoid modifying the original
const req = event.request.clone();
// Modify the request (e.g., add an authentication header)
const headers = new Headers(req.headers);
headers.append('Authorization', 'Bearer your_api_key');
const modifiedReq = new Request(req.url, {
method: req.method,
headers: headers,
body: req.body,
mode: 'cors',
credentials: req.credentials,
cache: req.cache,
redirect: req.redirect,
referrer: req.referrer,
integrity: req.integrity
});
// Make the modified request
fetch(modifiedReq)
.then(response => resolve(response))
.catch(error => {
console.error('Fetch error in Service Worker:', error);
// Optionally, return a default response or error page
resolve(new Response('Offline', { status: 503, statusText: 'Service Unavailable' }));
});
})
);
});
Denne koden avskjærer hver fetch
-forespørsel, kloner den, legger til en Authorization
-header, og utfører deretter den modifiserte forespørselen. event.respondWith()
-metoden forteller nettleseren hvordan den skal håndtere forespørselen. Det er avgjørende å klone forespørselen; ellers vil du modifisere den opprinnelige forespørselen, noe som kan føre til uventet oppførsel.
Den sørger også for å videresende alle opprinnelige forespørselsalternativer for å sikre kompatibilitet. Legg merke til feilhåndteringen: det er viktig å ha en reserveløsning i tilfelle fetch mislykkes (f.eks. når man er offline).
Eksempel: Legge til autentiserings-headere
Et vanlig bruksområde for avskjæring av forespørsler er å legge til autentiserings-headere i API-forespørsler. Dette sikrer at kun autoriserte brukere kan få tilgang til beskyttede ressurser.
self.addEventListener('fetch', event => {
if (event.request.url.startsWith('https://api.example.com')) {
event.respondWith(
new Promise(resolve => {
const req = event.request.clone();
const headers = new Headers(req.headers);
// Replace with actual authentication logic (e.g., retrieving token from local storage)
const token = localStorage.getItem('api_token');
if (token) {
headers.append('Authorization', `Bearer ${token}`);
} else {
console.warn("No API token found, request may fail.");
}
const modifiedReq = new Request(req.url, {
method: req.method,
headers: headers,
body: req.body,
mode: 'cors',
credentials: req.credentials,
cache: req.cache,
redirect: req.redirect,
referrer: req.referrer,
integrity: req.integrity
});
fetch(modifiedReq)
.then(response => resolve(response))
.catch(error => {
console.error('Fetch error in Service Worker:', error);
resolve(new Response('Offline', { status: 503, statusText: 'Service Unavailable' }));
});
})
);
} else {
// Let the browser handle the request as usual
event.respondWith(fetch(event.request));
}
});
Denne koden legger til en Authorization
-header i forespørsler som starter med https://api.example.com
. Den henter API-tokenet fra lokal lagring (local storage). Det er avgjørende å implementere riktig token-håndtering og sikkerhetstiltak, som HTTPS og sikker lagring.
Eksempel: Transformere forespørselsdata
Avskjæring av forespørsler kan også brukes til å transformere forespørselsdata før de sendes til serveren. For eksempel kan du ønske å konvertere data til et spesifikt format eller legge til ekstra parametere.
self.addEventListener('fetch', event => {
if (event.request.url.includes('/submit-form')) {
event.respondWith(
new Promise(resolve => {
const req = event.request.clone();
req.text().then(body => {
try {
const parsedBody = JSON.parse(body);
// Transform the data (e.g., add a timestamp)
parsedBody.timestamp = new Date().toISOString();
// Convert the transformed data back to JSON
const transformedBody = JSON.stringify(parsedBody);
const modifiedReq = new Request(req.url, {
method: req.method,
headers: req.headers,
body: transformedBody,
mode: 'cors',
credentials: req.credentials,
cache: req.cache,
redirect: req.redirect,
referrer: req.referrer,
integrity: req.integrity
});
fetch(modifiedReq)
.then(response => resolve(response))
.catch(error => {
console.error('Fetch error in Service Worker:', error);
resolve(new Response('Offline', { status: 503, statusText: 'Service Unavailable' }));
});
} catch (error) {
console.error("Error parsing request body:", error);
resolve(fetch(event.request)); // Fallback to original request
}
});
})
);
} else {
event.respondWith(fetch(event.request));
}
});
Denne koden avskjærer forespørsler til /submit-form
, parser forespørselskroppen som JSON, legger til et tidsstempel, og sender deretter de transformerte dataene til serveren. Feilhåndtering er essensielt for å sikre at applikasjonen ikke krasjer hvis forespørselskroppen ikke er gyldig JSON.
Respons-caching
Respons-caching innebærer å lagre responser fra API-forespørsler i nettleserens cache. Dette kan forbedre ytelsen betydelig ved å redusere antallet nettverksforespørsler. Når en cachet respons er tilgjengelig, kan nettleseren levere den direkte fra cachen, uten å måtte gjøre en ny forespørsel til serveren.
Fordeler med respons-caching
- Forbedret ytelse: Raskere lastetider og en mer responsiv brukeropplevelse.
- Redusert båndbreddeforbruk: Mindre data overføres over nettverket, noe som sparer båndbredde for både brukeren og serveren.
- Offline-funksjonalitet: Cachede responser kan leveres selv når brukeren er offline, noe som gir en sømløs opplevelse.
- Kostnadsbesparelser: Lavere båndbreddeforbruk fører til lavere kostnader for både brukere og tjenesteleverandører, spesielt i regioner med dyre eller begrensede dataabonnementer.
Implementering av respons-caching med Service Workers
Service Workers tilbyr en kraftig mekanisme for å implementere respons-caching. Du kan bruke Cache
API-et til å lagre og hente responser.
const cacheName = 'my-app-cache-v1';
const cacheableUrls = [
'/',
'/index.html',
'/styles.css',
'/script.js',
'https://api.example.com/data'
];
// Install event: Cache static assets
self.addEventListener('install', event => {
event.waitUntil(
caches.open(cacheName)
.then(cache => {
console.log('Caching app shell');
return cache.addAll(cacheableUrls);
})
);
});
// Activate event: Clean up old caches
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.filter(name => name !== cacheName)
.map(name => caches.delete(name))
);
})
);
});
// Fetch event: Serve cached responses or fetch from the network
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// Cache hit - return response
if (response) {
return response;
}
// Not in cache - fetch from network
return fetch(event.request).then(
response => {
// Check if we received a valid response
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// Clone the response (because it's a stream and can only be consumed once)
const responseToCache = response.clone();
caches.open(cacheName)
.then(cache => {
cache.put(event.request, responseToCache);
});
return response;
}
).catch(error => {
// Handle network error
console.error("Fetch failed:", error);
// Optionally, provide a fallback response (e.g., offline page)
return caches.match('/offline.html');
});
})
);
});
Denne koden cacher statiske ressurser under install-hendelsen og leverer cachede responser under fetch-hendelsen. Hvis en respons ikke finnes i cachen, hentes den fra nettverket, caches, og returneres deretter. `activate`-hendelsen brukes til å rydde opp i gamle cacher når Service Worker-en oppdateres. Denne tilnærmingen sikrer også at kun gyldige responser (status 200 og type 'basic') blir cachet.
Cache-strategier
Det finnes flere forskjellige cache-strategier du kan bruke, avhengig av applikasjonens behov:
- Cache-First: Prøv å levere responsen fra cachen først. Hvis den ikke finnes, hent den fra nettverket og cache den. Dette er bra for statiske ressurser og ressurser som ikke endres ofte.
- Network-First: Prøv å hente responsen fra nettverket først. Hvis det mislykkes, lever den fra cachen. Dette er bra for dynamiske data som må være oppdatert.
- Cache, then Network: Lever responsen fra cachen umiddelbart, og oppdater deretter cachen med den nyeste versjonen fra nettverket. Dette gir en rask innlasting og sikrer at brukeren alltid har de nyeste dataene (til slutt).
- Stale-While-Revalidate: Returner en cachet respons umiddelbart, samtidig som du sjekker nettverket for en oppdatert versjon. Oppdater cachen i bakgrunnen hvis en nyere versjon er tilgjengelig. Ligner på "Cache, then Network", men gir en mer sømløs brukeropplevelse.
Valget av cache-strategi avhenger av de spesifikke kravene til applikasjonen din. Vurder faktorer som hyppigheten av oppdateringer, viktigheten av ferskhet, og tilgjengelig båndbredde.
Eksempel: Caching av API-responser
Her er et eksempel på caching av API-responser ved hjelp av Cache-First-strategien:
self.addEventListener('fetch', event => {
if (event.request.url.startsWith('https://api.example.com')) {
event.respondWith(
caches.match(event.request)
.then(response => {
// Cache hit - return response
if (response) {
return response;
}
// Not in cache - fetch from network
return fetch(event.request).then(
response => {
// Check if we received a valid response
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// Clone the response (because it's a stream and can only be consumed once)
const responseToCache = response.clone();
caches.open(cacheName)
.then(cache => {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
} else {
// Let the browser handle the request as usual
event.respondWith(fetch(event.request));
}
});
Denne koden cacher API-responser fra https://api.example.com
. Når en forespørsel gjøres, sjekker Service Worker-en først om responsen allerede er i cachen. Hvis den er det, returneres den cachede responsen. Hvis ikke, gjøres forespørselen til nettverket, og responsen caches før den returneres.
Avanserte betraktninger
Cache-invalidering
En av de største utfordringene med caching er cache-invalidering. Når data endres på serveren, må du sørge for at cachen blir oppdatert. Det finnes flere strategier for cache-invalidering:
- Cache Busting: Legg til et versjonsnummer eller tidsstempel i URL-en til ressursen. Når ressursen endres, endres URL-en, og nettleseren vil hente den nye versjonen.
- Tidsbasert utløp: Sett en maksimal alder for cachede responser. Etter utløpstiden vil nettleseren hente en ny versjon fra serveren. Bruk
Cache-Control
-headeren for å spesifisere maksimal alder. - Manuell invalidering: Bruk
caches.delete()
-metoden for å manuelt fjerne cachede responser. Dette kan utløses av en hendelse på serversiden eller en brukerhandling. - WebSockets for sanntidsoppdateringer: Bruk WebSockets for å pushe oppdateringer fra serveren til klienten, og invalider cachen når det er nødvendig.
Innholdsleveringsnettverk (CDN-er)
Innholdsleveringsnettverk (CDN-er) er distribuerte nettverk av servere som cacher innhold nærmere brukerne. Bruk av et CDN kan forbedre ytelsen betydelig for brukere over hele verden ved å redusere ventetid og båndbreddeforbruk. Populære CDN-leverandører inkluderer Cloudflare, Amazon CloudFront og Akamai. Når du integrerer med CDN-er, må du sørge for at Cache-Control
-headerne er riktig konfigurert for optimal cache-oppførsel.
Sikkerhetshensyn
Når du implementerer avskjæring av forespørsler og respons-caching, er det viktig å vurdere sikkerhetsimplikasjonene:
- HTTPS: Bruk alltid HTTPS for å beskytte data under overføring.
- CORS: Konfigurer Cross-Origin Resource Sharing (CORS) riktig for å forhindre uautorisert tilgang til ressurser.
- Datasanering: Saner brukerinput for å forhindre cross-site scripting (XSS)-angrep.
- Sikker lagring: Lagre sensitive data, som API-nøkler og tokens, på en sikker måte (f.eks. ved å bruke HTTPS-only cookies eller et sikkert lagrings-API).
- Subresource Integrity (SRI): Bruk SRI for å sikre at ressurser hentet fra tredjeparts CDN-er ikke har blitt manipulert.
Feilsøking av Service Workers
Feilsøking av Service Workers kan være utfordrende, men nettleserens utviklerverktøy tilbyr flere funksjoner som kan hjelpe:
- Application-fanen: Application-fanen i Chrome DevTools gir informasjon om Service Workers, inkludert deres status, omfang og cache-lagring.
- Konsollogging: Bruk
console.log()
-setninger for å logge informasjon om Service Worker-aktivitet. - Breakpoints: Sett breakpoints i Service Worker-koden for å gå gjennom utførelsen trinn for trinn og inspisere variabler.
- Update on reload: Aktiver "Update on reload" i Application-fanen for å sikre at Service Worker-en oppdateres hver gang du laster siden på nytt.
- Avregistrer Service Worker: Bruk "Unregister"-knappen i Application-fanen for å avregistrere Service Worker-en. Dette kan være nyttig for å feilsøke problemer eller starte på nytt med blanke ark.
Konklusjon
Avskjæring av forespørsler og respons-caching er kraftige teknikker som kan forbedre ytelsen og brukeropplevelsen til webapplikasjoner betydelig. Ved å bruke Service Workers kan du avskjære nettverksforespørsler, modifisere dem etter behov, og cache responser for offline-funksjonalitet og raskere lastetider. Når de implementeres riktig, kan disse teknikkene hjelpe deg med å bygge høytytende, globalt tilgjengelige webapplikasjoner som gir en sømløs brukeropplevelse, selv under utfordrende nettverksforhold. Vurder de varierte nettverksforholdene og datakostnadene som brukere over hele verden står overfor når du implementerer disse teknikkene, for å sikre optimal tilgjengelighet og inkludering. Prioriter alltid sikkerhet for å beskytte sensitive data og forhindre sårbarheter.
Ved å mestre disse avanserte Fetch API-teknikkene, kan du ta webutviklingsferdighetene dine til neste nivå og bygge virkelig eksepsjonelle webapplikasjoner.