Promises, Async Await and Fetch — Network Requests in Modern JavaScript
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 Promise
just 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.