Promises, Async Await and Fetch — Network Requests in Modern JavaScript

Tomasz Chmielnicki
The Startup
Published in
4 min readOct 26, 2020

--

Photo by Clay Banks on Unsplash

ES6 introduced a brand new way of handling asynchronous actions and making network requests. Previously we would need to set up all the boilerplate required for XHR to make an API call, now we can simply do fetch(url). Awesome, right?

Promise.then

fetch is an asynchronous function. What this function returns is a Promise object. This kind of object has three possible states: pending, fullfilled and rejected. It always starts off as pending and then it either resolves or rejects. Once a promise resolves it runs the then method. This method takes a callback function as an argument and passes the resolved value to it. Take a look:

const url = 'https://randomfox.ca/floof/'fetch(url).then(response => {
console.log(response)
}

This is quite nice to read, isn’t it? Fetch the url then log the response.

Chaining multiple then

then, just like the asynchronous function that we originally called, fetch, also returns a Promise. What it really means is that we can chain as many thens as we want. If we return a value from the callback passed to then, the Promise returned by the then method will resolve with the callback’s return value. That value will be passed to the callback function of the next then. This might sound complicated but it really isn’t:

const url = 'https://randomfox.ca/floof/'fetch(url)
.then(response => {
return response.json())}
.then(parsedResponse => {
console.log(parsedResponse)
}

We call the API and it returns a json string. When that happens, in the then callback, we pass that response to the next then, converting it to a JavaScript object, using the .json method. There we can log the returned value.

Promise.catch

When an error occurs, Promise rejects and runs the catch method. Inside of this method we can handle the error. catch accepts a callback and passes the reason of rejection to it. We can chain a catch method with a then like this:

const url = 'https://randomfox.ca/floof/aaaa'fetch(url)
.then(response => {
return response.json()
})
.then(parsedResponse => {
console.log(parsedResponse.image)
})
.catch(reason => {
console.log(reason.toString())
})

We added catch at the end so that if anything goes wrong inside fetch or any of the attached thens, the catch at the end will handle it. catch returns a Promisejust like then. I changed the url to an invalid one so you can see that the catch block works.

As you can see, our code grows from top to bottom instead of getting deeply nested. That way it’s more readable than nested callback functions.

We can shorten our code like this if we want:

const url = 'https://randomfox.ca/floof/aaaa'fetch(url)
.then(response => response.json())
.then(parsed => console.log(parsed.image))
.catch(reason => console.log(reason.toString()))

Note that you can also pass the error handling function as the second argument to then instead of adding a catch block:

fetch(url).then(handleSuccess, handleFailure)

Async await

We can simplify our code using the ES7 async await syntax. It is simply a new way of handling Promises.

async function getFox() {
const url = 'https://randomfox.ca/floof/'
const res = await fetch(url)
const jsonRes = await res.json()
console.log(jsonRes.image)
}
getFox()

We use the async keyword in front of our function to declare an asynchronous function. In such function we can use the await keyword. We use it anytime we would use .then. Such expression pauses the execution of the function and returns the Promise’s value once it resolves.

Async functions return promises. That means we can use then on them:

async function getFox() {
const url = 'https://randomfox.ca/floof/'
const res = await fetch(url)
const jsonRes = await res.json()
return jsonRes
}
getFox().then(fox => console.log(fox.image))

Async await error handling

Just like we did with then, we can also add a catch:

async function getFox() {
const url = 'https://aaa'
const res = await fetch(url)
const jsonRes = await res.json()
return jsonRes
}
getFox()
.then(fox => console.log(fox.image))
.catch(reason => console.log(reason.toString()))

But we can also use try…catch:

async function getFox() {
try {
const url = 'https://aaa'
const res = await fetch(url)
const jsonRes = await res.json()
return jsonRes
} catch(e) {
console.log(e.toString())
}
}
getFox()
.then(fox => console.log(fox.image))

If anything in the try block goes wrong, control jumps to catch and passes the reason of rejection to it. Note that it will catch errors in asynchronous actions only if the await keyword is present in front. Otherwise the error will slip by.

Conclusion

The key takeaways here are:

We use then to grab a Promise’s fullfilled value:

fetch(url).then(console.log)

We can chain multiple then:

fetch(url)
.then(value => value.json())
.then(console.log)

To handle errors we use catch:

fetch(url)
.then(value => value.json())
.then(console.log)
.catch(console.log)

We can also declare an async function which allows us to use the await keyword instead of then and returns a Promise, so we can chain then and catch to the call of the function:

const doStuff = async () => {
const response = await fetch(url)
return await response.json()
}
doStuff()
.then(response => console.log(response.image))
.catch(console.log)

And that’s all you need to know to get started using fetch! Thanks for reading and check out this list of public APIs that you can play around with for inspiration.

--

--