Organising NodeJS' Error Handling Hell with IcedCoffeeScript
Let’s suppose you are building a NodeJS application. Lets focus on only one request (let it be our custom authentication despite of the fact that there are ready to use modules such as Passport). After you handled all edge cases your code may look like this:
app.post '/api/authentication/', (req, res)->
User.findOne
email: req.body.email
, (error, user)->
if error
res.json 504,
success: false
error_message: 'Database error while searching for user'
else
if not user
res.json 403,
success: false
error_message: 'User with given email was not found' + req.body.email
else
if user.authenticate req.body.password
# Save into session
req.session.userId = user.id
res.json
success: true
user: user.getPublicData()
else
res.json 403,
success: false
error_message: 'Email or password are wrong. Please check them and try one more time'
It looks quite deep. We can make it less deep by joining all if/else statements into one big if/else-if/else block:
app.post '/api/authentication/', (req, res)->
User.findOne
email: req.body.email
, (error, user)->
if error
res.json 504,
success: false
error_message: 'Database error while searching for user'
else if not user
res.json 403,
success: false
error_message: 'User with given email was not found' + req.body.email
else if user.authenticate req.body.password
# Save into session
req.session.userId = user.id
res.json
success: true
user: user.getPublicData()
else
res.json 403,
success: false
error_message: 'Email or password are wrong. Please check them and try one more time'
Now this is much better, but still there is room for improvement. Lets add following function:
errorHandler = {}
errorHandler.json = (res, code, message)->
res.json code,
success: false
error_message: message
Now we can rewrite our previous code to:
app.post '/api/authentication/', (req, res)->
User.findOne
email: req.body.email
, (error, user)->
return errorHandler.json res, 504, 'Database error while searching for user' if error
return errorHandler.json res, 403, 'User with given email was not found' if not user
return errorHandler.json res, 403, 'Email or password is wrong' if not user.authenticate req.body.password
# Save into session
req.session.userId = user.id
res.json
success: true
user: user.getPublicData()
This looks much better then what we have at the beginning. But can we do even better? Yes we can! Let’s use the power of ice (or simply IcedCoffeeScript). By awaiting the findOne user callback we can change our previous code into this piece of beauty:
app.post '/api/authentication/', (req, res)->
await User.findOne {email: req.body.email} , defer(error, user)
return errorHandler.json res, 504, 'Database error while searching for user' if error
return errorHandler.json res, 403, 'User with given email was not found' if not user
return errorHandler.json res, 403, 'Email or password is wrong' if not user.authenticate req.body.password
# Save into session
req.session.userId = user.id
res.json
success: true
user: user.getPublicData()
This way we shortened our code more then 2 times, changed maximal level of code depth from 7 to 2 thus making the code much more beautiful, readable and maintainable.