Error Handling
Open the project folder
In our starter files, open the index.html page from this lessons folder:
10.Async-JavaScript > 08.Error-handling
Introduction
In this lesson, we will discover some ways to handle errors in your async code. Code is great when things go well, but when things don’t, we need to handle what to do.
Some of the Promise methods we have looked at have given us some error handling. For example, the all()
method fails if one promise fails, so we have some options there.
But we also need to handle all other situations too, either a general error handler, or handling each specific request.
Project starter code
In the starter project, we have an empty image element, and a single function:
<body>
<img src="" alt="" />
<script>
async function getImageUrl() {
const response = await fetch('https://dog.ceo/api/breeds/image/random');
const image = await response.json();
return image.message;
}
</script>
</body>
Nothing new here for this function, it fetches the random image using async
/ await
, extracts the image URL, and returns it from the function.
Async functions
Just before we use this, take a look at a simple example:
function hey() {
return 'hey';
}
console.log(hey()); // hey
Nothing unexpected here. But if we mark this function as async
:
async function hey() {
return 'hey';
}
console.log(hey());
We now see a Promise
instead of a string:
Promise {<fulfilled>: 'hey'}
Remember, async
/ await
is built on promises too, and as soon as we mark a function as async
, it will also return a Promise
. Just bear this in mind for a moment.
Create a setImage() function
Back to the starter code, we have the function getImageUrl()
to return the random image. Let’s create another function to set the image in the browser:
// ...
async function setImage() {
// the getImageUrl function is async, so we await the promise
document.querySelector('img').src = await getImageUrl();
}
setImage()
Catching errors
Earlier when handling errors using promises, we chained a catch
block. This setImage
function is async
and therefore returning a promise, meaning we can use a catch
block:
setImage()
.catch(function (error) {
console.log('there was an error');
console.log(error);
});
📝 Remember, when chaining any method, the semi-colon must be removed from the previous line.
If all is working, you should see the image in the browser. To test out the catch
, we can switch off the network in the developer tools as we have done previously.
In Chrome, there will be an option in the network tab:

Set the offline option and you should see an error in the console:
⚠️ TypeError: Failed to fetch
Here we are mixing the syntax of async
/ await
and promises. If we wanted to stick with the promise syntax, or move the error handling into the function itself, we could use try
/ catch
.
The try
/ catch
statement
With try
/ catch
, we try
to do something, and if it works that is great. If not, we catch
the error and handle it how we want to. To see this, remove the existing catch
block we just added. Your code should now look like this:
async function getImageUrl() {
const response = await fetch("https://dog.ceo/api/breeds/image/random");
const image = await response.json();
return image.message;
}
async function setImage() {
document.querySelector("img").src = await getImageUrl();
}
setImage();
Inside the setImage
function, we can add a try
and a catch
block:
async function setImage() {
try {}
// catch is passed the error message
catch (error) {}
document.querySelector('img').src = await getImageUrl();
}
Then move the code we want to run inside the try
block:
async function setImage() {
try {
document.querySelector('img').src = await getImageUrl();
} catch (error) {}
}
setImage();
And then handle the error with catch
:
catch (error) {
console.log('there was an error');
console.log(error);
}
Give this a try with the network on and off. You should see the error messages in the console when the network is offline.
This try
/ catch
is run synchronously. Therefore it will run the try
section first, and if it fails, it will run the catch
block.
The finally
block
We also have the finally
block. This works just like when we chained finally
onto a promise. This will always run regardless of if the promise was fulfilled or rejected:
async function setImage() {
try {
document.querySelector('img').src = await getImageUrl();
} catch (error) {
console.log('there was an error');
console.log(error);
} finally {
console.log('always runs');
}
}
One of either the catch
or finally
blocks needs to be present, or we can use both as we have here. This is fine for general error catching. But what about if we wanted to know which part of the try
code failed?
Error handler function
For this we would chain a catch
block directly onto an async task. First, remove all the error handling from setImage
, leaving this:
async function setImage() {
document.querySelector('img').src = await getImageUrl();
}
Then, chain the catch
method inside the function:
async function setImage() {
document.querySelector("img").src = await getImageUrl().catch();
}
We could pass a function directly into catch()
, or if we wanted to re-use it, we could make a separate function:
// create error handler function
function handleError(error) {
console.log('there was an error');
console.log(error);
}
async function setImage() {
// pass error handler to catch
document.querySelector('img').src = await getImageUrl().catch(handleError);
}
setImage();
This function could then be re-used on multiple promises.
Of course, a console log wouldn’t be enough in a real app. We would want to maybe hide the image if there was an error, or add a placeholder, but the key is to do something rather than see the app or website break.
Handling errors is big part of async JavaScript, and we see some common patterns here which you can use in your projects to improve the functionality and user experience.