Jonas: So until now, we have always assumed
that everything went well with our Ajax calls.
So, we never handled any errors.
However, an important part of web development
is to actually handle errors,
because it's very common
that errors happen in web applications.
And so, in this lecture, let's talk about how
to handle errors in promises.
And to start, remember that a promise
in which an error happened is a rejected promise.
And so, in this video, we're gonna learn
how to handle promise rejections.
Now, actually, the only way in which the fetch promise
rejects is when the user loses his internet connection.
And so, for now, that's gonna be the only error
that we will handle here.
Now, to simulate losing the internet connection,
we can go here to Network
and then we can basically change the speed here to Offline.
However, when we then reload the page,
then basically, everything will disappear.
And so, that's not really what we want.
We want to simulate that the page was first still loaded,
but then as the user does the request without internet,
then we want to see the error happening.
And so, let's set it back to Online.
And so, now, what we want to do
is to basically only call this function here
whenever the user clicks on a button.
And so, that will then make it easier for us
to simulate losing the internet connection.
So here in the HTML, we already have a button.
So, let's comment this one out.
And then in the script, I believe, yeah.
So we already have it here selected as this btn.
And so, all we will do now is btn.addEventListener,
click, enter here, Function.
And just like this.
All right?
So, just to see what happens here.
So, indeed, that works just fine.
But now, watch what happens when we set ourselves offline.
Let's go back to the console.
And so, now, when we do the request,
then we get these errors here.
So, first, this one here,
but the one that's most important
is that we now have an uncaught promise.
And so, because we have failed to fetch.
And so, at this point for the first time,
the promise that's returned from the fetch function
was actually rejected.
And so, let's now handle that rejection.
Now, there are two ways of handling rejections,
and the first one is to pass a second callback function
into the then method.
So, the first callback function here
is always gonna be called for the fulfilled promise.
So, for a successful one.
But we can also pass in a second callback,
which will be called when the promise was rejected.
So, let's do that.
And this callback function will be called with an argument,
which is basically the error itself.
And so, let's simply alert the error.
All right.
So, let's try that.
Oh, and now, we are back to having no internet.
So we first need to do everything normally.
So, load the page.
Now, we lose the connection,
and now, we do the request.
And so, now actually, we handled the error
by displaying this alert window.
And the error that we saw previously
down here is now gone.
So, now in fact,
we no longer have this uncaught error down here,
because we did actually catch the error right here.
So, handling the error is also called catching the error.
And so, again, that's the reason why
this error that we had here before disappeared.
So, it said uncaught error,
but again, now, we are actually catching it here.
And so, with this, we are now handling the error
that might occur in this promise here.
Okay?
But now, what if there was actually no error
in this fetch promise here?
So, basically, what if this fetch promise
was actually fulfilled,
but then the second one here was rejected?
Well, then we would also have to catch an error here.
So, we would have to come here,
get this function and paste it here,
and also handle the error right here.
However, that is a little bit annoying,
and so, in fact, there is a better way
of basically handling all these errors globally
just in one central place.
So instead of all of these callback functions here,
let's just delete them.
Okay?
So, this is a lot nicer
just having one callback in the then.
And then instead, we can handle all the errors
no matter where they appear in the chain
right at the end of the chain by adding a catch method.
And then here, we can actually use
the same callback function,
because the callback function here will also be called
with the Error object that occurred,
and so then we can handle it in some way.
So, again, this catch method here at the end of the chain
will basically catch any errors that occur
in any place in this whole promise chain,
and no matter where that is.
So, errors basically propagate down the chain
until they are caught.
And only if they're not caught anywhere,
then we get that unco error
that we saw right in the beginning.
All right.
So let's now try this again,
and we will have to put back the internet,
reload the page, get back offline,
and indeed, we get the same error now here.
So, the same alert window that we had before.
Now, instead of having that annoying alert window,
let's just log the error to the console
and create some string here.
So, error, and then let's just add some emojis
so that we know exactly
which errors were printed by ourselves.
Well, let's add a couple of them here.
And in fact, we can actually also style this one
with console.error.
Remember?
I think I showed you this one
at some point in the beginning of the course.
But anyway, usually simply logging the error to the console
is not enough in a real application
with a real user interface.
And so, instead of just logging something to the console,
let's also display an error message for the user to see.
And so, that's then a more real use case
of this catch block.
All right.
So, I'm adding a new block here.
So we still want to, of course,
log the error to the console, but besides that,
let's actually now create a function
that will also render some kind of error.
Okay?
So let's do that outside here,
and I'm calling it renderError.
So, this is a function and it can take in a message.
Just call it like this.
And just like the country elements,
we want to attach this to countriesContainer,
and then insertAdjacent.
And this time, not HTML, but insertAdjacentText.
So, this is a new one, but basically, it does the same thing
as insertAdjacentHTML, but simply with text.
So, it doesn't create any new HTML elements.
Okay?
And then besides that,
remember we always have to set the opacity back to 1.
So just as we did before in the renderCountries function,
because otherwise, this container is not even be visible.
And actually, let's put this function
right at the very top of the file
together with the render country function.
So, just like this.
Okay?
So, again, in both cases,
in order to actually see the container,
we set the opacity to 1.
Okay?
And so now here, let's use that.
render, so not country, of course, Error.
Let's say, "Something went wrong."
Then here, we can use our emoji again.
Why not?
And then here, let's again print the error.
And actually, this error that is generated here
is a real JavaScript object.
So we can create errors in JavaScript with a constructor,
for example, just like a map or a set,
and any error in JavaScript that was created like this
contains the message property.
So we can use that here to basically only print the message
of that error and not the whole object itself.
And we will actually learn more
about the builtin Error object of JavaScript
in the next video.
Now, to try this, we need to again get ourselves online,
then offline, check the console,
and indeed, we get our custom error down here
visible by the emojis,
and then our own message here for the user.
Now, it's not super pretty, but that's not the point here.
And as you see, indeed, this here is simply
the error.message while down here, we have the entire error.
So that includes the type error
and even it includes this so-called stack trace.
So it shows us exactly where the error comes from.
All right.
And so, that's how we handle errors happening
in promises in any then handler.
So basically, handling any promise rejection
no matter where it happens in the chain.
Now, just to finish, there is one more quick method
that I want to show you
and that is also available on all promises.
So, besides then and catch,
there is also the finally method.
So, let's add a finally here, finally.
And then the callback function
that we define here will always be called
whatever happens with the promise.
So, no matter if the promise is fulfilled or rejected,
this callback function that we define here
is gonna be called always.
So that's the difference between the other two.
So, the then method is only called when the promise
is fulfilled, while this one is only called
while the promise is rejected.
Now, the finally method is not always useful,
but sometimes, it actually is.
So, we use this method for something
that always needs to happen
no matter the result of the promise.
And one good example of that is to hide a loading spinner
like these rotating circles
that you see everywhere in web applications
when you load some data.
So, these applications show a spinner
when an asynchronous operation starts
and then hide it once the operation completes.
And that happens no matter
if the operation was successfully or not.
And so, for that, the finally method is perfect.
And in our case, what we always need to do
is to fade in the container.
So, basically, this part here always happens
no matter what, right?
So, no matter if we render the country
in the case of success
or if we render the error in case of an error,
no matter what, we always need to do this.
And so, let's take this out of here.
So I'm copying it here and deactivating this in both cases,
and then let's put it here into the finally handler.
Okay?
And now, we no longer need the offline.
So let's just reload, put it here,
and yeah, that works beautifully.
Just notice that this actually works
because catch itself also returns a promise.
So, that's the only way why this here can work.
So, of course, this only works on promises.
And so, yeah.
Again, this can only work if catch itself
also returns a promise.
All right.
Now, let's try to simulate another error
so that I can show you what we need to do next.
So, let's say that we're trying to search for a country
that simply doesn't exist.
And so, or API is not gonna find any result for that.
Let's say just this.
So, let's see.
And well, we get this weird error
that we cannot read property flag of undefined in line 152.
So, basically, it's coming from this place,
but, of course, then in our stack trace
we can see where exactly it's coming from.
So, it's coming from getCountryData.
Well, in this case, it's not that helpful,
but sometimes, inspecting the stack trace
can be helpful indeed.
So, anyway, this error here is weird
and it doesn't really reflect the true error,
which is simply that our API cannot find any country
with this name.
So, the true error is of course not
that we cannot read flag of undefined,
but, in fact, it is that our API cannot find any country.
And so, that's reflected here with the status code of 404.
However, as I said in the beginning,
the fetch promise only rejects
when there is no internet connection.
But with a 404 error like this, which is not a real error,
but well, it kind of is, but anyway, with this 404,
the fetch promise will still get fulfilled.
So, there is no rejection.
And so, our catch handler cannot pick up on this error.
It does pick up on this other error.
So, this one here,
but that's not the one that we actually want to handle.
In this case, we really want to tell the user
that no country was found with this name.
And so, that is what we will do in the next lecture,
so that this one doesn't become all too long.