JavaScript promises and fetch()

Pete Hanner
7 min readOct 4, 2019

As developers, we often need to gather data from external sources for use in our own programs. Doing this in JavaScript used to require clunky code or the use of outside libraries, but fortunately the Fetch API has in recent years made retrieving, adding, editing, and removing data from external databases easier than ever before. This API works by providing and resolving a series of promises. Let’s break down this process with a few examples.

A basic fetch

At its core, fetch only requires one argument: the location where it should send its request (which by default is a GET request). So if we wanted to access the Star Wars API (SWAPI) and find the fourth character in the database, we could use the following JavaScript code:

const SWAPI_URL = "https://swapi.co/api/people/4/";fetch(SWAPI_URL)

Running that code will execute without errors, but it won’t… well… do anything. Examining the result of this code in the terminal, we’re left with the following:

Our JavaScript has given us a promise. “Okay, I’ve sent a request to the server to GET information from the URL you gave me, and I promise I’ll do something with it.”

We haven’t told our program what to do with the request results, though, so let’s do that now. But first, there’s another step we have to deal with. SWAPI is formatted in JSON, as are many APIs. JavaScript Object Notation is text that looks like a JavaScript object when formatted properly, but it’s actually just a string. When the server gives us a string, we need to convert that into a JS Object that our program can actually use. Fortunately this is quite simple:

fetch(SWAPI_URL)
.then(response => response.json() )

(For the sake of space we’re cutting out the actual SWAPI_URL assignment for the rest of this article, but just remember it’s the const defined in the first code snippet)

Note that we use the name response inside the then call, but this is just convention and you can name it whatever you want. Common names also include resp, rsp, and r, as long as it’s clear what you’re doing in the code. When we execute this code and look at our console, we see the following:

Another promise! See a pattern emerging? Our JavaScript program is telling us, “Okay, I made a GET request to the server address you provided, I fulfilled my promise of turning that into a usable JavaScript Object, and now I promise I’ll do something else with that Object.” At this point, the world is our oyster! We have a usable JavaScript object, and we can do whatever we want with it! For starters, let’s just see what the object actually is that we’ve gotten back from the server:

fetch(SWAPI_URL)
.then(response => response.json())
.then(result => {
console.log(result)
});

Again, the wordresult is just a name that we’ve chosen as shorthand for "the usable Object converted from JSON we got back from the server". While using response for the initial fetch processing is fairly standard and intuitive, you might be working with all kinds of Objects for all kinds of reasons, so be careful in what name you use for the JSON-converted Object. It should be succinct, descriptive, and accurate to what the Object actually is. Let’s see what we have:

Excellent! We receive another promise (which is what .then() does under the hood), but then that promise resolves with whatever instructions we put in the block. In this case, we’ve just printed the JS Object out to our console, but we can give it any kind of instructions we want. Let’s manipulate the DOM with our fancy new Object, this time giving it a more descriptive name:

fetch(SWAPI_URL)
.then(response => response.json())
.then(darthVaderObj => {
let name = document.createElement('h2');
name.innerText = `Name: ${darthVaderObj['name']}`
let birth = document.createElement('h3');
birth.innerText = `Born ${darthVaderObj['birth_year']}`
document.body.appendChild(name)
document.body.appendChild(birth)
});

Opening that in the browser results in the following:

Voila! It may not look like much, but consider what just happened: someone else put this huge database of information on the internet, just raw information waiting to be used. In less than a dozen lines of code, we accessed that information and were able to tell our own program what to do with it. This is a simple example, but the implications are huge. As long as you can get the data from the server, you can do whatever you need to do with it.

That said, don’t be too proud of this technological terror you’ve constructed. The ability to retrieve remote API data is insignificant next to the power of the Force.

Something worth noting: because .then() returns a promise, you have to do your work within the block of your fetch’s promise chain. If you try to assign a variable outside of the fetch, you get something like the following:

let darth;fetch(SWAPI_URL)
.then(response => response.json())
.then(darthVaderObj => {
darth = darthVaderObj;
})
console.log(darth)

Whomp. There are workarounds, but they’re kinda sloppy:

let darth = [];fetch(SWAPI_URL)
.then(response => response.json())
.then(darthVaderObj => {
darth.push(darthVaderObj);
})
console.log(darth)

It “works”, but you’re better off just doing the work you need to do inside the fetch promise.

Moving beyond GET

This is handy for getting data from an API, aka the R in CRUD, but what if we need to interact with the database in other ways? Where is C-UD? Good news, we can use that same fetch promise chain with relatively little additional complexity.

If we want to add additional data to the API or edit existing data, we need to tell the server what kind of request we’re making (POST or PATCH) and give it the actual data to add or change. Technically you can do this all in one giant chunk, but it’s easiest to assign the new API data (the body) to a variable, assign the HTTP request specifications (the config) to a variable, and give that to the fetch request in a nice, neat package. Let’s see what this looks like in action.

After a climactic battle over the forest moon of Endor, Darth Vader has gained redemption and returned to the light side of the Force as Anakin Skywalker. Before cremating his father’s body in a moving, John Williams-scored montage, Luke realizes he needs to send a PATCH request to update SWAPI appropriately.

“Give me one second real quick while I update my package manager.”

First, he assigns the relevant changes to a variable:

let bodyData= {'name': 'Anakin Skywalker'}

Next, he sets up an object that tells the fetch() call what kind of HTTP request to make and how. Note the body line: we are converting the bodyData variable defined above from a JavaScript Object into a JSON string. This step is essential for the communication between our program and the server.

let configObj = {
method: 'PATCH',
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify(bodyData)
};

With these steps out of the way, it’s now actually quite simple for Luke to send his request to the server — he just gives this configuration Object to fetch() as an additional argument, letting it know to deviate from its default behavior.

fetch(SWAPI_URL, configObj)

Non-GET requests are a little bit more open-ended with what happens after we make them. By default, we just get an HTTP request code back from the server letting us know whether the operation succeeded or failed. Sometimes that’s all we need; for example, the following code would just print a message to our console based on the request status:

fetch(SWAPI_URL, configObj)
.then(rsp => {
if (rsp.ok) {
console.log('Success!')
}
})
.catch(error => console.log(error.message))

We can imagine, however, that when editing an object within an API, we might want to get the edited object back for further work. This will require some editing of our backend behavior; for example, if we’re using Rails to build our API, we would need to add this final line to the update method in our relevant controller:

render json:<updated_object_name>

Obviously, replace <updated_object_name> with the relevant variable name in your own program. This instructs update to send the modified object in JSON format back to the client once it’s finished executing. Once we receive the updated JSON, we can convert and manipulate it just like a normal GET request:

fetch(SWAPI_URL, configObj)
.then(rsp => rsp.json())
.then(updatedObj => {
console.log(updatedObj)
})
.catch(error => console.log(error.message))

In this case we’re just console logging the updated object, but we could manipulate and use it in any way we needed like we could with a standard fetch() request. This process would work pretty much exactly the same with a POST request. We would use a similar configObj for a DELETE request, but since they’re removing API information, they don’t take a body, and obviously can’t send an object back, so we’d just be customizing behavior based on a success/failure server response.

This just scratches the surface of using fetch(), but hopefully this examination of the basics will be useful in understanding the details as you delve into more specific uses for your own apps. Good luck, and may the Force be with you!

Sebastian Shaw and Yub Nub or GTFO.

--

--

Pete Hanner

Former paralegal gladly opting for programming instead of law school. Engaged in a years-long, steady migration northward.