Your First Ajax Call With Xmlhttprequest

Last post, calling a supplier for data was still just a metaphor. This one actually places the call, the old-school way, with XMLHttpRequest.

July 11, 20266 min read2 / 22

In the last post, calling a supplier for data was a metaphor for AJAX. Time to actually place that call in real code.

JavaScript gives you a few different ways to do this. The oldest one is a built-in object called XMLHttpRequest, and it's worth learning even though it's clunky, because plenty of existing code out there still uses it, and it makes the "waiting for data" problem from the last post impossible to ignore.

Creating and Opening the Request

Three lines get a request ready to go.

JavaScript
const request = new XMLHttpRequest(); request.open('GET', 'https://countries-api-836d.onrender.com/countries/name/portugal'); request.send();

new XMLHttpRequest() creates the object that's going to manage this whole exchange. .open('GET', url) tells it what kind of request this is (GET, meaning "give me data" rather than "here's data for you") and where to send it, but nothing actually goes out over the network yet. Only .send() fires it off.

That URL points at a free, public API that returns data about countries. If you go looking for other public APIs, this one's a fair example of what to check for first: it needs no authentication, and its CORS setting is open, which matters a lot more than it sounds. Without CORS enabled, a server can flatly refuse to let your JavaScript, running on a completely different domain, read its response at all.

Why You Can't Just Read the Result on the Next Line

Here's the part that actually matters. Add a read right after .send() and it won't work, the response isn't there yet.

JavaScript · Live Editor
Loading editor...

Hit Run and watch both lines land. The first one prints instantly, and it's empty, because it runs the moment .send() returns, before any response has arrived. .send() fires the request off and hands control straight back to your script, it doesn't wait around for a reply. This is the exact non-blocking behavior from the last post, just with a real network request standing in for the timer.

The second line is the one that actually works, and it prints a moment later, once the real response lands. this inside that callback refers to the request object, so this.responseText and request.responseText mean the same thing here. The difference is timing: this callback only runs once the response has actually arrived, so reading responseText inside it works.

The XMLHttpRequest lifecycle: creating the request, opening it, sending it, the rest of the script continuing immediately, and the load event firing once the response actually arrives. ExpandThe XMLHttpRequest lifecycle: creating the request, opening it, sending it, the rest of the script continuing immediately, and the load event firing once the response actually arrives.

Turning JSON Into Something You Can Use

What actually lands in responseText is JSON, which, as covered last post, is just a string. Before you can pull population or region out of it, you have to parse it into a real object.

JavaScript
const request = new XMLHttpRequest(); request.open('GET', 'https://countries-api-836d.onrender.com/countries/name/portugal'); request.send(); request.addEventListener('load', function () { const [data] = JSON.parse(this.responseText); console.log(data); });

Searching an API by country name returns an array with one match in it, so destructuring straight into data skips a step you'd otherwise need. Run this and you'll see a full object: capital, flag, region, population, languages, currencies, and a fair amount you won't use.

Building a Card From Real Data

With a real object in hand, rendering it is just a template literal and one DOM call.

JavaScript · Live Editor
Loading editor...

Hit Run and the card renders below. population comes back as a plain number like 10305564, which isn't something you'd want to show a reader, so +data.population / 1000000 converts and scales it, and .toFixed(1) rounds it to something readable like 10.3. languages and currencies both come back as arrays (a country can have more than one of each), so [0].name just takes the first.

Two Calls at Once, in No Particular Order

Wrap the request itself in a function and you can reuse it for any country.

JavaScript · Live Editor
Loading editor...

Hit Run a few times. Both requests go out almost immediately, before either response has come back. They run in parallel, not in sequence, so whichever server happens to respond first is the card that renders first. You'll sometimes see Germany appear above Portugal, and sometimes the other way around. That's not a bug, it's the exact non-blocking behavior from the last post made visible: firing off the second request never waited on the first one finishing.

That unpredictability is fine here, since the two cards don't depend on each other. It stops being fine the moment one request actually needs to happen after another, say, looking up a country and then looking up its neighboring country by name. Making that work in order means nesting the second request's code inside the first one's callback, and the one after that inside the second, and so on.

That nesting has an actual name among developers: callback hell. It's the exact problem that pushes JavaScript toward a cleaner way of handling asynchronous code, called Promises, which is next.

The Essentials

  1. A request is created, opened, and sent in three separate steps: new XMLHttpRequest(), .open('GET', url), .send(). Nothing goes out over the network until .send() runs.
  2. The response isn't available on the next line after .send(). You register a load event listener and read this.responseText inside it, once the data has actually arrived.
  3. API responses arrive as JSON text. JSON.parse() turns that string into a real object you can pull data out of.
  4. Firing off multiple requests back to back runs them in parallel, not in order. Whichever response arrives first renders first, this is the non-blocking behavior from the last post, now visible in a real network call.
  5. Getting results back in a guaranteed order means chaining requests, nesting each one inside the previous one's callback. That nesting is called callback hell, and it's exactly the problem Promises exist to solve.