So far we've been discussing async functions when everything is going fine. No database timeout, no validation errors, no sudden loss of network. But these things happen, so we need to prepare for them too. Without proper error propagation, failures stall Promises and async functions, stopping them forever.
With synchronous functions, errors happen when something throw
s them, and they bubble up until there is a try-catch
:
const fn = () => {
throw new Error("Something bad happened");
}
try {
fn();
}catch(e) {
// handle error
}
Async functions work similarly so that when there is an error thrown it will go up until a try-catch
. When there is an asynchronous error (we'll look into how they work in the next chapter), you can handle it with the same familiar structure:
const fn = async () => {
throw new Error("Something bad happened");
}
try {
await fn();
}catch(e) {
// handle error
}
Errors are thrown during the await
and not when the function is called:
const asyncFn = async () => {
throw new Error("Something bad happened");
}
const res = asyncFn(); // no error here
try {
await res; // error
}catch(e) {
// handle error
}
But when you use the Promise constructor, you need to pay attention to propagate the errors.
Let's revisit our previous examples! The gapi.load
loads a client library that can be used with Google services and it needs a callback to notify when it's finished. This is easy to turn into a Promise with the Promise constructor:
await new Promise((res) => {
gapi.load("client:auth2", res);
});
// auth2 loaded
But what happens when there is an error? Maybe the network is down and the client library can not be loaded. In this case, the callback function is never called and the Promise is never finished. An await
waiting for it will wait forever.
await new Promise((res) => {
gapi.load("client:auth2", res); // error
});
// never happens