skip to content

In XMLHttpRequest, what does setting `xhr.responseType = 'json'` change about how you read the result, and why does reading `xhr.responseText` afterwards throw?

level: juniorimportance: should knowfreq 38%

answer

  1. one property, several output shapes
  2. everything lands on the same getter
  3. the old getters are type-guarded
  4. empty or 'text' only, or it throws
  5. bad JSON gives null, not an error

basics

~20 s

Setting XMLHttpRequest's responseType to 'json' makes the browser parse the body for you and expose the parsed value on xhr.response. Reading xhr.responseText then throws an InvalidStateError, because that getter is only legal when responseType is the empty string or 'text'.

solid answer

~40 s

`responseType` tells `XMLHttpRequest` what shape you want the body delivered in: `''` (the default) and `'text'` give a string, `'json'` gives an already-parsed value, `'blob'` a `Blob`, `'arraybuffer'` an `ArrayBuffer`, and `'document'` a parsed DOM `Document`. Whichever you pick, the result always arrives on `xhr.response`. The two legacy getters are typed and guard themselves: `responseText` throws `InvalidStateError` unless `responseType` is `''` or `'text'`, and `responseXML` throws unless it is `''` or `'document'`. So once you opt into `'json'`, `xhr.response` is the only way in. One trap: with `'json'`, a malformed body does not raise — `xhr.response` is simply `null`. Set `responseType` after `open()` and before `send()`; changing it once the response is arriving throws `InvalidStateError`.

code

javascript · 12 lines
javascript
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/user');
xhr.responseType = 'json';
xhr.addEventListener('load', () => {
  console.log(xhr.response); // parsed value, or null if the body was not JSON
  try {
    console.log(xhr.responseText);
  } catch (err) {
    console.log(err.name); // "InvalidStateError"
  }
});
xhr.send();

go deeper

for a junior

Remember that xhr.response is the property that always holds the result, and that responseText only works for text responses. Saying plainly that 'json' means the browser parsed it for you is enough here.

for a middle

Be ready to list the six responseType values and state the exact guard on responseText and responseXML, including the InvalidStateError they throw. Mention that a bad JSON body yields null rather than an exception.

for a senior

Show the operational consequence: a silent null from a proxy's HTML error page produces a TypeError far from the cause, so explain when you would decode text and parse yourself in order to keep the raw body for logging.

for a principal

Frame it as a contract decision — whether client code should trust browser-side parsing at all, or standardise on a response envelope plus explicit parsing so that malformed payloads and content-type drift are observable in telemetry rather than silent nulls.

## What responseType actually does `XMLHttpRequest` receives bytes. `responseType` is the instruction that tells the browser what to turn those bytes into before handing them to your code. It accepts exactly six values: | value | `xhr.response` holds | | --- | --- | | `''` (default) | a string, decoded as text | | `'text'` | a string, decoded as text | | `'json'` | the value produced by parsing the body as JSON | | `'blob'` | a `Blob` | | `'arraybuffer'` | an `ArrayBuffer` of the raw bytes | | `'document'` | a parsed `Document` (HTML or XML) | Everything lands on the single `xhr.response` property. That is the modern surface, and it is the one to reach for. ```js const xhr = new XMLHttpRequest(); xhr.open('GET', '/api/user'); xhr.responseType = 'json'; xhr.addEventListener('load', () => { console.log(xhr.response.name); // already an object, no JSON.parse }); xhr.send(); ``` ## Why responseText throws `XMLHttpRequest` predates `response`. The original API had `responseText` (a string) and `responseXML` (a `Document`), and nothing else. When `responseType` was added, those two getters were not generalised — they were fenced off, so that a program cannot ask for a string from an object that was never decoded as text. The rules are mechanical: - `responseText` throws `InvalidStateError` if `responseType` is anything other than `''` or `'text'`. - `responseXML` throws `InvalidStateError` if `responseType` is anything other than `''` or `'document'`. So the failure people hit is: they copy an old snippet that reads `xhr.responseText`, then add `xhr.responseType = 'json'` to avoid a manual `JSON.parse`, and the handler now throws on the first line. The fix is to read `xhr.response`. ## Parse failures are silent With `responseType = 'json'`, the browser parses the body once the response is complete. If the body is not valid JSON — an HTML error page from a proxy, a truncated body, an empty 200 — the parse failure is swallowed and `xhr.response` is `null`. No exception is thrown, and the `error` event does not fire (that event is reserved for network-level failure). Code that does `xhr.response.items.forEach(...)` then dies with a `TypeError` on `null` far away from the real cause. If you need to distinguish "server sent nothing" from "server sent garbage", use `responseType = 'text'` and parse yourself, so the `SyntaxError` is yours to catch: ```js try { const data = JSON.parse(xhr.responseText); } catch (e) { // you now know the body was not JSON, and you still have the raw text to log } ``` ## When you may set it `responseType` is a settable property, not an argument to `open()`, and the state machine constrains it: - Setting it while the response is already streaming in or finished — `readyState` of `LOADING` (3) or `DONE` (4) — throws `InvalidStateError`. - Setting it to anything other than `''` on a **synchronous** request in a document context makes `open()` throw `InvalidAccessError`. Typed responses are async-only on the main thread. The safe habit is: construct, `open()`, set `responseType`, attach listeners, `send()`. ## Partial reads `responseType` also decides whether you can watch the body arrive. With `''` or `'text'`, `xhr.responseText` returns however much has been decoded so far while `readyState` is `LOADING`, which is how old streaming-ish code worked. With `'json'`, `'blob'`, `'arraybuffer'` or `'document'` there is nothing meaningful to hand over half-way, so `xhr.response` is `null` until the request reaches `DONE`. ## What it does not do `responseType` is purely a client-side decoding instruction. It sends no `Accept` header, does not ask the server for a particular format, and does not validate the response's `Content-Type`. If you set `'json'` and the server returns XML, you get `null` — not a negotiation error. If you want the server to know what you want, set the header yourself with `xhr.setRequestHeader('Accept', 'application/json')`. The one place `Content-Type` does matter is `'document'`: the browser uses the response's media type to decide whether to parse as HTML or XML, and gives you `null` for a type it will not parse as a document at all.

  • With responseType set to 'json', how would you tell a genuinely empty response apart from a body that failed to parse?
    You cannot from `xhr.response` alone — both surface as `null`. Check `xhr.status` and `xhr.getResponseHeader('Content-Length')` for the empty case, or drop to `responseType = 'text'` and call `JSON.parse` yourself so a malformed body raises a `SyntaxError` you can catch and log with the raw text attached.
  • Can you read xhr.response before the request finishes?
    Only for text. With `responseType` of `''` or `'text'`, `xhr.responseText` returns the portion decoded so far while `readyState` is `LOADING` (3). For `'json'`, `'blob'`, `'arraybuffer'` and `'document'` there is no meaningful partial value, so `xhr.response` stays `null` until `readyState` reaches `DONE` (4).
  • Does setting responseType change what the browser sends to the server?
    No. It is purely a client-side decoding instruction — no `Accept` header is added and no content negotiation happens. If you want JSON from the server you must call `xhr.setRequestHeader('Accept', 'application/json')` yourself. The only server-side input `responseType` consults is the response `Content-Type`, and only for `'document'`, to choose the HTML or XML parser.

saying these in an interview costs you the question

  • Thinks responseText works regardless of responseType
  • Believes malformed JSON fires the error event
  • Assumes responseType sets the Accept request header
  • Says responseType can be changed while the response streams in
  • Confuses xhr.response with xhr.responseXML for JSON bodies

context