JavaScript Promise error handling with catch and finally
Let's see how to handle errors in JavaScript Promises. You might think you can put a try/catch around a Promise:
try {
const p = new Promise((resolve, reject) => {
console.log("I think I'm about to throw up!");
throw new Error('boom');
});
p.then(() => console.log("I'm never called."));
} catch (e) {
console.log("I'm never called too!");
}
When you run the code above, the error won't be caught and the script will crash. The try/catch (in this case) is not able to catch the Promise error. But you can catch it like this:
const p = new Promise((resolve, reject) => {
console.log("I think I'm about to throw up!");
throw new Error('boom');
});
p.catch((e) => console.log(e.message));
There is a catch
method chained to the promise p
. And this catch
catches errors that happen in the Promise chain before the catch. For instance:
new Promise((resolve, reject) => {
throw new Error('boom');
})
.then(() => console.log('I never run.'))
.catch((e) => console.log(e.message)); // It shows boom
It's recommended to have a catch at the end of all promise chains.
Sometimes you might want to always execute something after a catch
or a then
. For that you use a finally
:
new Promise((resolve, reject) => {
resolve('all good');
})
.then((v) => console.log(v)) // Logs "all good"
.catch((e) => console.log(e))
.finally(() => console.log('finally!')); // Logs "finally!"
If the previous promise was rejected, it would still work. You can put finally anywhere in the promise chain:
new Promise((resolve, reject) => resolve('all good'))
.then((v) => console.log(v)) // Logs "all good"
.finally(() => console.log('finally!')) // Logs "finally!"
.catch((e) => console.log(e))
// Logs "finally again!"
.finally(() => console.log('finally again!'));
Error handling gotcha: reject vs catch
By now you might be thinking that the reject handler is the same as catch:
// A, the catch catches the error
new Promise((resolve, reject) => reject('boom'))
.then(null)
.catch((e) => console.log(e)); // Logs boom
// B, the `then` method handles the
// error (notice the second argument of the then function)
new Promise((resolve, reject) => {
reject('boom');
}).then(null, (e) => console.log(e)); // Logs boom
In this case it is exactly the same. But what happens if the resolve function throws an error?
const badOnResolve = () => {
throw new Error('boom on resolve');
};
// A, the catch catches the error
new Promise((resolve, reject) => resolve('boom'))
.then(badOnResolve)
.catch((e) => console.log(e.message)); // Logs "boom on resolve"
// B, the `then` method handles the error
// (notice the second argument)
new Promise((resolve, reject) => {
resolve('looks good here');
}).then(badOnResolve, (e) => console.log('onReject'));
// Doesn't log "onReject", it throws an uncaught error.
It doesn't catch the error! If the resolve has an issue, only a catch or the next onReject can catch the error. The next A and B are similar:
const badOnResolve = () => {
throw new Error('boom on resolve');
};
// A
new Promise((resolve, reject) => {
resolve('looks good here');
})
.then(badOnResolve)
.then(null, (e) => console.log(e.message));
// B
new Promise((resolve, reject) => {
resolve('looks good here');
})
.then(badOnResolve)
.catch((e) => console.log(e.message));
Errors inside any handler on the
then
method can only be treated on the next reject handler or the next catch.
The following diagram may help:
And in any case, remember that
catch(func) is the same as then(undefined, func)
Reject Promise vs Throw Promise
You might think rejecting or throwing inside a Promise is the same:
new Promise((resolve, reject) => {
reject(new Error('boom'));
}).catch((e) => console.log(e.message)); // boom
new Promise((resolve, reject) => {
throw new Error('boom');
}).catch((e) => console.log(e.message)); // boom
This is all fine, but if you throw an error inside an asynchronous call inside the Promise function, it won't work:
new Promise(() => {
// An asynchronous callback.
setTimeout(() => {
throw new Error('boom');
}, 0);
}).catch((e) => console.log("I don't show!"));
The result is an uncaught error. If you use reject
, you won't have this problem:
new Promise((resolve, reject) => {
// An asynchronous callback.
setTimeout(() => reject(new Error('boom')), 0);
}).catch((e) => console.log(e.message)); // boom
Error on reject
We've been passing an error to the reject function. Why don't we just pass a message like we do for the resolve? The reason is because an error contains information about the stack trace:
new Promise((resolve, reject) => {
reject(new Error('boom'));
}).catch((e) => console.log(e.message, e.stack));
// Shows "boom" and the stack trace.
It's recommended to always send an error on the reject to have extra information about the error.
Questions
What's the output?
const p = new Promise((resolve, reject) => {
throw new Error('boom');
});
p.catch((e) => console.log(e.message));
What's the output?
const logErr = (e) => console.log(e.message);
new Promise((resolve, reject) => {
reject(new Error('boom'));
}).catch(logErr);
new Promise((resolve, reject) => {
throw new Error('boom');
}).catch(logErr);
What's the output?
const log = (e) => console.log(e.message);
new Promise((resolve, reject) =>
setTimeout(() => reject(new Error('boom')), 0)
).catch(log);
What's the output?
const log = (e) => console.log(e.message);
new Promise(() =>
setTimeout(() => {
throw new Error('boom');
}, 0)
).catch(log);
What's the output?
Promise.resolve('ok')
.then(console.log)
.then(() => {
throw new Error('boom');
})
.then(console.log)
.catch((e) => console.log(e.message));
What's the output?
Promise.resolve('ok1')
.then(() => {
throw new Error('nok');
})
.catch((e) => console.log(e.message))
.then(() => console.log('ok2'));
What's the output?
const p = new Promise((resolve, reject) => {
reject('boom');
});
p.catch(console.log);
What's the output?
const p = new Promise((resolve, reject) => {
throw new Error('boom');
reject(new Error('baam'));
});
p.catch((e) => console.log(e.message));
What's the output?
new Promise((resolve, reject) => {
throw new Error('boom');
})
.then(() => console.log('hi'))
.catch((e) => console.log(e.message));
What's the output?
new Promise((resolve) => {
resolve('resolve');
})
.then(console.log)
.then(console.log)
.catch(console.log)
.finally(() => console.log('fin'));
What's the output?
new Promise((resolve) => resolve('ok'))
.then((v) => console.log(v))
.finally(() => console.log('fin1'))
.catch((e) => console.log(e))
.finally(() => console.log('fin2'));
What's the output?
const p = new Promise((_, reject) => reject("rej"))
p.then(null).catch(console.log);
p.then(null, (e) => console.log(e));
What's the output?
const f = () => {
throw new Error('boom');
};
const p = new Promise((res) => res('ok'));
const logErr = (e) => console.log(e.message);
p.then(f).catch(logErr);
p.then(f, logErr);
What's the output?
const f = () => {
throw new Error('boom');
};
const p = new Promise((res) => res('ok'));
const logErr = (e) => console.log(e.message);
p.then(f).then(null, logErr);
p.then(f).catch(logErr);