Async & Await
Fetching "Synchronously" with Async/Await
So far we have written fetch code like this:
fetch('https://pokeapi.co/api/v2/pokemon/pikachu')
.then((response) => {
if (!response.ok) {
return console.log(`Fetch failed. ${response.status} ${response.statusText}`)
}
return response.json()
})
.then((responseData) => {
console.log("Here is your data:", responseData);
// do something with the response data
})
.catch((error) => {
console.log("Error caught!");
console.error(error.message);
})We have taken the Promise returned by fetch() and used the .then and .catch methods to schedule callbacks to execute when the promise resolves/rejects.
However, an alternate syntax was created to achieve the same effect but in a more "synchronous-like" manner. This approach utilizes the async and await keywords
The
awaitkeyword causes our code to pause and wait for the Promise to resolve. It then unpacks the Promise and returns the resolved value.The
asynckeyword does two things:First, it labels a function as asynchronous. This is required for any function that makes use of the
awaitkeywordSecond, it wraps the function’s returned value in a Promise. If we were to store the returned value of
getPikachuData(), it would be a Promise.
Handling Errors with Try/Catch
There are some functions (like fetch()) that are capable of throwing an error.
We can manually throw our own errors too using throw new Error('message'). When an error is thrown, the program crashes immediately:
try/catch is the standard way to handle those errors and it will prevent the application from crashing. Instead, we can handle any errors that occur and continue on:
So, when using a function that can throw an error like fetch() or response.json(), we should always use try and catch:
The benefits of async/await
async/awaitUsing the async/await syntax with try and catch has a number of benefits. The main ones being readability and debuggability.
We can write async code in a synchronous-like manner
We avoid having to write a bunch of callbacks
We can avoid common mistakes made when using callbacks
try/catchis a more general-purpose way of handling errors that can be used for more than just fetching.
Making a generic fetch helper
The code for fetching data is almost always the same:
In a
tryblock,fetchfrom a URL and parse the response as JSONIn a
catchblock, log the caughterror. Any error that occurs in thetryblock will be caught by this one sharedcatchblock
So, we can refactor our code a bit, add in some safety measures, and create a helper function that abstracts away this logic:
Let's break down this fetchData helper
It accepts a
urland anoptionsargument allowing other types of requests to be made (POST, PATCH/PUT, DELETE, etc...). If the caller offetchDatadoes not provideoptions, it will default to an empty object.If the
!response.okguard clause is triggered, an error is thrown instead of returning. This let's us handle4xxand5xxresponses in thecatchblock and treat them the same as errors thrown byfetchandresponse.json().It checks the content type of the
responseto determine how to parse (withresponse.json()orresponse.text())It returns the data in a "tuple" format — an array with 2 values where the first value is always the data (if present) and the second value is always the error (if present). Only one of the two values will ever be present.
With the helper built, we can use it like this
Why return a tuple?
You may be wondering, why couldn't we write this helper function such that it just returns the data if there are no errors, or returns the error if there is one?
The reason we don't do this is to make the code that uses this function cleaner. The code that uses fetchData will need to know if the data it receives is an error or JSON data. The problem is that error objects and the jsonData can often be difficult to differentiate. An error object will have a message property, and often times, so do JSON response objects! Take the Dog Image API as an example. It will return its data like this:
Since error objects and jsonData objects can look so similar in their structure, we can't simply check the structure of the returned object to know if it was an error or JSON data. We could do something like this:
But if our fetchData function always returns a [data, error] tuple where one of those values will ALWAYS be null while the other is defined, then the code that uses fetchData will become much cleaner:
Last updated