Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
Working with APIs  ›  Lesson

XMLHttpRequest

XMLHTTPRequest 8 minXMLHttpRequest is the older, event-based way to make network requests: you create an object, attach onload/onerror listeners, then call send() — the response arrives later as an event, not through a Promise, which is why modern code almost always reaches for fetch() instead.

XMLHttpRequest

In short: XMLHttpRequest (XHR) is the original way browsers made network requests, long before Promise or fetch existed. Instead of returning a Promise, it reports its result through events: you create an XMLHttpRequest, attach onload/onerror listeners, and only then call .send(). Whichever event fires — success or failure — your listener reacts. It still appears in older codebases and a few APIs fetch doesn't cover (like upload progress), but modern code almost always reaches for fetch instead. This lesson's code talks to a real network, which this sandbox can't do — read it, don't run it.

const xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.onload and xhr.onerror set up as listeners; xhr.send() sent last — a note that there's no Promise, no await: you set up listeners first, then send, and whichever event tunes in reacts; modern code reaches for fetch() instead

Requesting a playlist the old way

The classic shape: create, configure, attach listeners, then send — always in that order.

function loadPlaylist(onSuccess, onFailure) {
  const xhr = new XMLHttpRequest();
  xhr.open("GET", "/api/playlist");
  xhr.onload = () => {
    if (xhr.status >= 200 && xhr.status < 300) {
      onSuccess(JSON.parse(xhr.responseText));
    } else {
      onFailure(new Error(`request failed: ${xhr.status}`));
    }
  };
  xhr.onerror = () => {
    onFailure(new Error("network error"));
  };
  xhr.send();
}

loadPlaylist(
  (songs) => console.log(songs.length, "songs loaded"),
  (err) => console.log("failed:", err.message)
);

xhr.open configures the request but doesn't send it. onload fires once a response arrives, whatever its status — checking xhr.status by hand is how you tell success from an HTTP error, since onload itself doesn't distinguish them. onerror is the genuine network-failure path, similar to a fetch rejection. Listeners must be attached before .send(), because the event could fire as soon as a response comes back.

Reading the response as it's parsed by hand

Unlike fetch's response.json(), XHR hands you raw text — parsing is your responsibility:

const xhr = new XMLHttpRequest();
xhr.open("GET", "/api/songs/1");
xhr.onload = () => {
  const song = JSON.parse(xhr.responseText); // manual parsing
  console.log(song.title);
};
xhr.send();

xhr.responseText is always a plain string; there's no built-in .json() convenience the way fetch's Response has one. Forgetting to JSON.parse it is a common XHR mistake — you'd be working with a string that merely looks like an object.

Tracking progress, one thing XHR still does well

One genuine reason XHR still shows up: it can report upload progress, which plain fetch cannot without extra machinery.

const xhr = new XMLHttpRequest();
xhr.open("POST", "/api/upload");
xhr.upload.onprogress = (event) => {
  if (event.lengthComputable) {
    const percent = Math.round((event.loaded / event.total) * 100);
    console.log(`uploaded ${percent}%`);
  }
};
xhr.onload = () => console.log("upload complete");
xhr.send(/* some file data */);

upload.onprogress fires repeatedly as the data streams out, giving a percentage for something like a progress bar — a real capability gap fetch still has, which is why XHR hasn't fully disappeared.

🎯 Your turn

Explain why xhr.onload = () => { ... } must be attached before xhr.send() is called, and what would likely happen if a developer called send() first and attached onload afterwards.

Quick practice

quick drill

How does XMLHttpRequest report that a request has finished?

quick drill

Does xhr.onload fire for an HTTP error response like a 404?

quick drill

What does xhr.responseText give you?

Practice — level up

0/1 solved

Write your answer, then compare it with a rubric and a model answer.

ExplainListeners before send

Explain why xhr.onload = () => { ... } must be attached BEFORE xhr.send() is called, and what would likely happen if a developer called send() first and attached onload afterwards.