A `Cache` object from the browser's Cache API offers `add(request)`, `addAll(requests)` and `put(request, response)`. What does each one do, and when do you have to use `put`?
answer
- one fetches for you, one takes what you hold
- all-or-nothing for a list
- non-2xx is refused by only one of them
- a body can be read once
basics
~20 sadd(request) fetches a URL and stores the result; addAll(list) does that for several URLs and stores none if any fails. put(request, response) stores a Response you already hold — the only option when you already fetched it.
solid answer
~50 s`add()` is a shorthand: it performs the fetch for you and stores the response under that request. `addAll()` is the same for a list and is all-or-nothing — if any request fails or answers with a non-`ok` status, the whole promise rejects and nothing is written, which is exactly what you want for pinning one version's assets. `put()` takes a `Request` (or URL string) and a `Response` you already have, and stores the pair without judging it — so it accepts a 404, a redirect, or an opaque cross-origin response that `add()` would reject. You need `put()` whenever the response came from somewhere else: a fetch you already made and want to return to the page as well, a `no-cors` cross-origin fetch, or a `Response` you constructed in JavaScript. Because a body can only be read once, store `response.clone()` when you also intend to use it.
code
javascript · 19 linesasync function demo() {
const cache = await caches.open('demo-v1');
// add/addAll fetch for you and refuse non-2xx responses
await cache.addAll(['/', '/app.js']);
// put stores what you already hold - clone so the caller can read it too
const res = await fetch('/api/config');
await cache.put('/api/config', res.clone());
// put also accepts a Response you constructed yourself
await cache.put('/offline', new Response('<h1>Offline</h1>', {
headers: { 'Content-Type': 'text/html' }
}));
console.log((await cache.keys()).map((r) => r.url));
return res.text();
}
demo();go deeper
Recall that add/addAll fetch for you while put stores a response you already hold, and that a response body must be cloned if two places need it.
Explain the rejection rules — add refuses non-ok, opaque and 206 responses — and why addAll being all-or-nothing matters when pinning one version's assets.
Discuss when storing a non-2xx deliberately is correct, how overwrite-by-matching-key interacts with Vary, and how you keep a precache step from writing a partially populated generation.
Frame it as a consistency question: what constitutes an atomic asset set for a release, how a failed precache is detected and retried, and what the app does when the pinned set is incomplete.
## The three write paths A `Cache` stores pairs of `Request` (the key) and `Response` (the value). There are exactly three ways to write into one. **`cache.add(request)`** takes a URL string or a `Request`, performs a `fetch()` for it, and stores the result. It is a convenience wrapper — roughly `fetch(req).then(res => cache.put(req, res))` — with one important extra rule: it rejects rather than storing a response that is not usable. **`cache.addAll(list)`** does the same for an array, and is **all-or-nothing**. Every request is fetched, and only if all of them succeed does the cache get written. If one URL 404s, the promise rejects and none of the others are stored either. That property is why it is the natural tool for pinning the assets of one application version: you never end up with a half-populated cache where three of five files exist. **`cache.put(request, response)`** stores a pair you already hold. It does no fetching and applies almost no policy. ```js const cache = await caches.open('v1'); await cache.addAll(['/', '/app.js', '/app.css']); // all or nothing const res = await fetch('/api/config'); await cache.put('/api/config', res.clone()); // keep res usable return res; ``` ## What `add`/`addAll` refuse The rejection rules are the practical difference. `add()` and `addAll()` reject with a `TypeError` when the fetch fails outright, when the response status is not an ok status (that is, outside 200–299), or when it is a `206 Partial Content`. An **opaque** response — what a cross-origin `no-cors` fetch produces — has status `0`, which is not ok, so `add()` can never store one. `put()` applies far fewer checks. It will happily store a `404`, a `500`, or an opaque response. It still enforces a few structural rules: the key request's method must be `GET`, its URL scheme must be `http` or `https`, and a `206` response is rejected here too, because a partial body is not a meaningful cache entry. So the rule of thumb is: use `add`/`addAll` when the browser fetching it for you is fine and you want failures to be loud; use `put` when you already have the response object, when the response is opaque, or when you deliberately want to store something that is not a 2xx. ## The body-consumed trap A `Response` body is a stream and can be consumed once. `cache.put()` consumes it. This is the single most common bug in this area: ```js // broken: the page gets an already-consumed body const res = await fetch(url); cache.put(url, res); return res; // TypeError: body stream already read ``` `response.clone()` produces a second readable copy, and it must be called **before** either copy is read. Which one you cache and which one you return does not matter; what matters is that two independent bodies exist. The same applies to `Request` objects when you need to both inspect and forward one. ## Storing something you made yourself `put()` accepts any `Response`, including a constructed one. That is how offline fallbacks are usually pinned without a network round trip: ```js await cache.put( '/offline', new Response('<h1>Offline</h1>', { headers: { 'Content-Type': 'text/html' } }) ); ``` ## Overwrite semantics `put()` replaces any existing entry whose key matches the new request — matching uses the same rules a lookup does, including `Vary`. There is no duplicate-key error and no append mode; the last write for a given key wins. To remove an entry explicitly you call `cache.delete(request)`, which resolves to `true` if something was removed. ## Reading back what you wrote `cache.keys()` resolves with the stored `Request` objects in insertion order — the fastest way to see what a cache actually contains while debugging, because the keys are requests, not bare URL strings. Reading is always `match()`/`matchAll()`; there is no index-based access.
- Why does `cache.addAll()` store nothing when a single URL in the list fails?Because it is specified as atomic: every response is fetched and validated first, and the cache is only updated if all of them are acceptable. That makes it safe for pinning one application version — you never get a cache holding three of five files, which would otherwise produce an app that half-loads offline.
- What happens if you try to `put()` a response under a POST request as the key?It rejects with a `TypeError`. Cache API keys must be GET requests with an http or https URL. If you need to cache something derived from a POST, you have to invent a synthetic GET key — a URL that encodes the relevant parameters — and store the response under that instead.
- You fetched a response, cached it, and the page then received an empty body. What went wrong?`cache.put()` consumed the body stream, and a `Response` body can only be read once. Call `response.clone()` before either read happens and cache the clone while returning the original — or the reverse. Cloning after one copy has already been read is too late and throws.
saying these in an interview costs you the question
- Thinks add() and put() differ only in argument count
- Assumes addAll() stores whatever succeeded and skips failures
- Passes the same Response to both put() and the page without clone()
- Believes put() rejects non-2xx responses
- Tries to cache a POST request as the key