Networking, Storage, and Workers
HTTP methods and caching semantics, choosing among cookies/Web Storage/IndexedDB, offline strategy with service workers, and offloading CPU-bound work to a web worker.
By the end of this lesson, you can
- Choose the right HTTP method and read a Cache-Control header's basic directives
- Choose among cookies, Web Storage, and IndexedDB based on size, lifecycle, and server-visibility trade-offs
- Explain what a service worker intercepts and why that enables an offline strategy
- Offload CPU-bound work to a web worker and communicate with it via message passing
Why it matters
Asynchronous Basics and Data Fetching treated fetch
as a black box that returns JSON. Real applications need to understand what’s actually happening
over HTTP, choose the right place to persist data client-side, and — for CPU-heavy work or offline
support — reach beyond the main thread entirely.
HTTP methods and status classes
| Method | Purpose | Idempotent? |
|---|---|---|
| GET | Retrieve a resource, no side effects | Yes |
| POST | Create a resource, or a non-idempotent action | No |
| PUT | Replace a resource entirely | Yes |
| PATCH | Partially update a resource | Not guaranteed |
| DELETE | Remove a resource | Yes |
Recall idempotent from Asynchronous JavaScript in
Depth — a GET/PUT/DELETE is safe to retry
automatically; POST generally isn’t. Status codes fall into five classes: 1xx informational, 2xx
success, 3xx redirection, 4xx client error (the request itself was wrong), 5xx server error
(something failed on the server’s side, even with a valid request).
Caching with Cache-Control
Cache-Control: max-age=3600, must-revalidate
Cache-Control tells the browser (and any intermediate cache) how long a response can be reused
without asking the server again. max-age sets that duration in seconds; no-store disables
caching entirely; no-cache means “always revalidate with the server before using a cached copy,”
which — despite the name — doesn’t disable caching, just requires a check first.
Choosing where to store client-side data
| Storage | Size limit | Sent to server automatically? | Best for |
|---|---|---|---|
| Cookies | ~4KB per cookie | Yes — with every matching request | Session/auth tokens the server needs to see |
| localStorage / sessionStorage | ~5–10MB | No | Small client-only data — preferences, a draft, a flag |
| IndexedDB | Large (browser-dependent, often GBs) | No | Structured, queryable, offline-capable app data |
A cookie being sent automatically with every matching request is a double-edged trait: convenient
for authentication, but a real cost on every request whether needed or not — and a genuine security
surface, which is why an auth cookie should carry HttpOnly (inaccessible to JavaScript, mitigating
XSS token theft) and Secure (HTTPS-only) flags, set by the server.
Service workers and offline strategy
A service worker is a script that runs separately from any page, capable of intercepting every network request the page makes and deciding how to respond — from the network, from a cache, or a combination:
// sw.js — registered from the page with navigator.serviceWorker.register('/sw.js')
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cached) => {
return cached || fetch(event.request); // cache-first strategy
}),
);
});
Different offline strategies fit different data: cache-first (above) suits assets that rarely change; network-first-with-cache-fallback suits data that should be fresh when possible but usable when offline; a documented, deliberate choice per resource type matters more than any single “correct” strategy.
Web workers for CPU-bound work
A web worker runs JavaScript on a genuinely separate thread — useful specifically for CPU-heavy work (image processing, complex calculations) that would otherwise block the main thread and freeze the UI. Workers communicate with the main thread by message passing, not shared memory:
// main.js
const worker = new Worker('calc-worker.js');
worker.postMessage({ numbers: [1, 2, 3 /* ...thousands more */] });
worker.onmessage = (event) => {
console.log('Result:', event.data.result);
};// calc-worker.js
self.onmessage = (event) => {
const result = event.data.numbers.reduce((sum, n) => sum + expensiveCalculation(n), 0);
self.postMessage({ result });
};Data passed via postMessage is copied using the structured clone algorithm — recall
structuredClone() — not shared by reference, so a worker
can never directly mutate the main thread’s objects (or vice versa); everything crosses as a copy.
Check your understanding
Or reveal the answer without checking
Answer:Yes — it stores it, but must revalidate with the server before reusing it on a later request
Despite the name, no-cache doesn't disable caching — it requires revalidation with the server before the cached copy can be used. no-store is the directive that actually prevents storing a response at all.
Or reveal the answer without checking
Answer:IndexedDB
IndexedDB is designed for large amounts of structured, queryable data with an offline-capable lifecycle. Cookies are too small and add overhead to every request; localStorage/sessionStorage are synchronous, string-only, and impractical at this scale.
Practice: warm-up
For each scenario, name the best-fit client-side storage: (1) a short-lived “dismissed this banner” flag, (2) an authentication token the server must see on every request, (3) an offline product catalog with hundreds of searchable items.
Stuck? Reveal one hint at a time.
Hint 1
A flag that only this tab/session needs, and doesn't need to survive a browser restart, has a storage option matched almost exactly to that lifecycle.
Hint 2
Something the server needs to see automatically on every request is the defining trait of one specific storage type.
Hint 3
Large, structured, queryable data points toward the storage designed for exactly that.
Reveal the answers
Try the problem yourself before reading this. There is often more than one reasonable approach — treat this as one worked example, not the only correct answer.
1. A dismissed-banner flag -> sessionStorage (or localStorage if it should persist across sessions)
2. An auth token the server must see -> a cookie (ideally HttpOnly + Secure)
3. An offline, searchable product catalog -> IndexedDBPractice: apply it
Sketch (as commented pseudocode) a service worker fetch handler implementing a network-first,
cache-fallback strategy: try the network first, and only fall back to a cached response if the
network request fails.
Stuck? Reveal one hint at a time.
Hint 1
This is the inverse of the cache-first example in the lesson — try fetch() first, inside a try/catch or a .catch().
Hint 2
On failure, fall back to caches.match(event.request); if neither succeeds, there's genuinely no response to give.
Reveal a reference solution
Try the problem yourself before reading this. There is often more than one reasonable approach — treat this as one worked example, not the only correct answer.
self.addEventListener('fetch', (event) => {
event.respondWith(
fetch(event.request).catch(() => {
return caches.match(event.request); // network failed — fall back to cache
}),
);
});Summary
GET/PUT/DELETEare idempotent and generally safe to retry automatically;POSTusually isn’t. Status codes group into five classes, 4xx meaning the request was wrong and 5xx meaning the server failed on a valid request.Cache-Control: no-cachestill caches — it forces revalidation before reuse;no-storeis what actually disables caching.- Cookies are sent automatically with every matching request (a cost and a security surface); Web Storage is simple, synchronous, and client-only; IndexedDB fits large, structured, offline-capable data.
- A service worker intercepts every network request a page makes, enabling deliberate, documented offline strategies — cache-first for stable assets, network-first-with-fallback for data that should be fresh when possible.
- A web worker runs on a separate thread for CPU-bound work, communicating with the main thread only via message passing — data is copied via structured clone, never shared by reference.