important! errorHandler caveats
There are some caveats and gotchas that you have to understand and take care of while developing the application and relying on Routine’s error handling.
Let’s start with the infamous errorHandler
global function which is like a global try catch
block for your application
That sounds good, what’s the catch (pun not intended) ?!
Well, let’s start with some code examples and work our way down from there
This would work fine
const Routine = require('routinejs')
const app = new Routine({
catchErrors: true,
})
app.get('/user', (req, res) => {
try {
throw new Error('something')
} catch (e) {
console.log('catch')
}
})
const server = app.listen(8080)
Well, since the error being thrown inside /user
route controller is synchronous
in nature, it’s handling would be transferred to it’s try catch
block, and if that block is not present, then handling would be transferred to the errorHandler
function, given that catchErrors
value is not explicitly set to false
But something like this would not work
const Routine = require('routinejs')
const app = new Routine({
catchErrors: true,
})
app.get('/user', (req, res) => {
try {
//async error
Promise.reject('promise rejected')
} catch (e) {
console.log('catch')
}
})
const server = app.listen(8080)
The reason it won’t work is that when dealing with async errors
inside Routine
, you have two options, deal with those errors yourself by setting catchErrors
value to false
, or let Routine
do it for you with it’s global error handler called errorHandler
in the Routine
constructor.
While designing the library, we had to take some assumptions into account, one of which is that if catchErrors
is true
, it means there are very minimal try catch
blocks present in the user’s code base.
If for some reason you do not want the errorHandler
to take control of a specific async
error, you have to chain a .catch
method directly to the Promise
This example will work fine
const Routine = require('routinejs')
const app = new Routine({
catchErrors: true,
})
app.get('/user', (req, res) => {
Promise.reject('promise rejected').catch((err) => res.json('inside catch'))
})
const server = app.listen(8080)
Now the ownership of the error above would not be passed to errorHandler
but rather it’s chained .catch
methods, same goes if you have a Promise rejection
inside a chained .catch
Now, let’s talk about errorHandler
in a bit more detail
errorHandler
function is supposed to act like a global try catch
, but what if an error
occurs inside this function itself
glad you asked, let’s dissect this problem
Let’s deal with synchronous
errors first…
errorHandler
will not catch sync
errors happening inside it’s own body without a proper try catch
system in place, this is done deliberately to avoid infinite loops of errorHandler
execution (this happens though when we are talking about async
errors)
For example, this program will crash
const Routine = require('routinejs')
const app = new Routine({
catchErrors: true,
errorHandler: (error, req, res) => {
//this will cause the program to crash
throw new Error('causing crash here')
},
})
app.get('/user', (req, res) => {
//error so that we can go into errorHandler body above
throw new Error('sync error')
})
const server = app.listen(8080)
To fix the above program, simply wrap the errorHandler
body in a try catch
errorHandler: (error, req, res) => {
try {
//now, catch will be called
throw new Error("this won't crash now")
} catch (e) {
console.log(e)
}
}
Things are not so simple when talking about async
errors
There are scenarios where the program can go in an infinite code execution loop because of async
errors happening inside errorHandler
This program will go in an infinite loop of errorHandler
const Routine = require('routinejs')
const app = new Routine({
catchErrors: true,
errorHandler: (error, req, res) => {
console.log('error handler called')
try {
setTimeout(() => {
throw new Error(err)
}, 500)
} catch (e) {
console.log('catch')
}
},
})
app.get('/user', (req, res) => {
//error so that we can go into errorHandler body above
throw new Error('sync error')
})
const server = app.listen(8080)
Notice, even though we have a try catch
in place inside errorHandler
, the program itself will keep printing error handler called
message, that is because of the phenomenon we learned earlier in the article, when we have an async
error in place and catchErrors
is set to true
, we need explicit chaining of .catch
otherwise try catch
is of no use.
This program will NOT crash
const Routine = require('routinejs')
const app = new Routine({
catchErrors: true,
errorHandler: (error, req, res) => {
Promise.reject('inside error handler').catch((e) => res.json(e))
},
})
app.get('/user', (req, res) => {
//error so that we can go into errorHandler body above
throw new Error('sync error')
})
const server = app.listen(8080)
This will cause the program to send the error as json
to the user because that’s what we are doing inside the .catch
method