What does the browser's navigator.sendBeacon(url, data) call return, and what does that return value actually tell you about whether the server received the data?
answer
- boolean, returned immediately
- queued, not delivered
- false means refused up front
- no Response, no status code
- payload size budget applies
basics
~20 snavigator.sendBeacon returns a boolean immediately: true means the browser queued the data for sending, false means it refused to queue it, usually because the payload was too large. True is not a delivery receipt, and no response is ever readable.
solid answer
~50 s`navigator.sendBeacon()` returns a boolean, not a promise, and it returns it immediately. `true` means the user agent has accepted the payload into its own send queue — it has taken ownership and will attempt the POST even after the document is gone. `false` means it would not even queue it; the usual cause is that the body pushes the document past its in-flight keepalive budget of roughly 64 KiB, but a bad or blocked URL does it too. The point interviewers are testing is that `true` is a queueing acknowledgement, not a delivery confirmation. There is no `Response` object, no status code and no error event, so a beacon rejected by the server with a 500, dropped by the network, or blocked by an extension is indistinguishable from a successful one as far as the page can tell.
code
javascript · 11 linesfunction report(payload) {
const body = JSON.stringify(payload);
if (navigator.sendBeacon('/collect', body)) return true;
// Refused (usually too large): keep it for the next page load.
try {
localStorage.setItem('pendingReport', body);
} catch {
// storage full — the report is dropped on purpose
}
return false;
}go deeper
Know the shape of the call: it returns a boolean straight away, never a promise, and true only means the browser queued the payload. Say plainly that you cannot read a response from a beacon.
Explain why false comes back — chiefly the in-flight keepalive body budget of about 64 KiB — and what your code does about it, such as truncating the payload or deferring it to the next page load.
Show that you design the endpoint for a client that can never observe failure: idempotent writes, tolerance for duplicates, and server-side monitoring, because the page has no retry signal to act on.
Own the tradeoff between a fire-and-forget beacon and a fetch with keepalive across a whole product: what data is allowed to be lossy, what must be acknowledged, and how you measure the loss rate you have accepted.
## The signature `navigator.sendBeacon(url, data)` takes a URL and an optional body and returns a `boolean`. The body may be any `BodyInit` value except a stream — a string, a `Blob`, an `ArrayBuffer` or typed-array view, `FormData`, or `URLSearchParams`. The request is always a POST; there is no argument for the method and none for headers. ```js const ok = navigator.sendBeacon('/collect', JSON.stringify({ sessionMs: 41230 })); if (!ok) { // the browser never took the payload — decide what to do about it now } ``` The call does not block. It hands the payload to the browser and returns on the same turn, which is exactly why it is usable in a handler that runs while the page is being torn down: nothing about it needs the document to survive. ## What `true` means `true` means one thing only: the user agent has successfully queued the data for transfer. Ownership of the payload has moved from the page to the browser process, and the browser will keep the request alive independently of the document — after the tab is closed or a new page has committed. That is the whole value proposition of the API. It is not a statement about the network, the server, or the response. The request may still fail DNS, be refused by the server, return a 404 or a 500, or be blocked cross-origin. None of that comes back to your code. ## What `false` means `false` means the browser refused the payload up front. The dominant cause in practice is size: beacons draw on the same in-flight keepalive quota as `fetch(..., { keepalive: true })`, and that quota is about 64 KiB of body across all in-flight keepalive requests for the document. A 300 KB error dump returns `false` every time. Other causes are a URL the page is not allowed to request — a scheme the browser rejects, or a destination blocked by the page's Content Security Policy `connect-src` directive. `false` is actionable, and it is the only signal the API gives you. Treat it as "this data is gone unless I do something": shrink or truncate the payload, drop low-value fields, split one large beacon into several small ones, or stash the payload in `localStorage` and send it on the next page load. Code that ignores the return value silently loses exactly the sessions with the most interesting data, because the biggest payloads are the ones that fail. ## Why there is no response The API is deliberately fire-and-forget. Reading a response would require a live JavaScript context to receive it, and the whole point of a beacon is that it outlives the context that created it. Handing the page a promise that could never settle would be worse than handing it nothing. So the design gives you a synchronous accept/reject on the queueing step and nothing after that. The consequence for design is that a beacon endpoint must be built to fail silently and safely on the server side: it should never rely on the client seeing a status code, never expect the client to retry on error, and should tolerate duplicates, because the client cannot tell a delivered beacon from a lost one and a retry-on-next-load strategy will inevitably double-send some payloads. ## When you need more than a boolean If you actually need the response — a server-assigned id, a validation error, an auth failure — a beacon is the wrong tool. Use `fetch()` with `keepalive: true` instead: it returns a promise resolving to a real `Response`, and if the document is still alive when the response arrives, you can read it. You give up nothing in delivery guarantees; you only take on the responsibility of handling a rejected promise, and you inherit the same ~64 KiB budget. ## Recap `true` = queued, `false` = refused. Neither says anything about the server. Check the return value, plan for `false`, and reach for `fetch` with `keepalive` when the answer matters.
- If sendBeacon returns false, what would you actually do next?Treat the payload as unsent. Shrink it — drop verbose fields, truncate stack traces — and try again, or split it into several beacons under the budget. If it still will not queue, persist it in localStorage and send it on the next page load, accepting the risk that the user never returns.
- Which body types can you pass, and does the choice change anything on the wire?String, Blob, ArrayBuffer or a typed-array view, FormData, and URLSearchParams. The choice sets the Content-Type the server sees: a string becomes text/plain, URLSearchParams becomes application/x-www-form-urlencoded, FormData becomes multipart/form-data, and a Blob carries whatever type you gave it.
- Can a beacon be blocked by the page's Content Security Policy?Yes. A beacon is a network request from the document, so the connect-src directive applies to it. If the endpoint's origin is not allowed by connect-src, the request is blocked and sendBeacon reports failure rather than silently succeeding.
saying these in an interview costs you the question
- Says true means the server received the data
- Expects a promise from sendBeacon and awaits it
- Tries to read a status code from a beacon
- Ignores a false return and loses the payload
- Assumes any payload size will be queued