Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Understanding Error Handling in Node.js

Understanding Error Handling in Node.js

Presentation slides for Holberton School in San Francisco, Feb 15th 2018.

Jacopo Daeli

February 15, 2018
Tweet

More Decks by Jacopo Daeli

Other Decks in Programming

Transcript

  1. Understanding Error Handling in Node.js Jacopo Daeli Lead Software Engineer

    @ GoDaddy San Francisco, Feb 15th 2018 Holberton School
  2. Who am I? u I'm a Computer Scientist, Software Engineer

    and Hacker, passionate about web technologies. I develop with Node.js, Go, C/C++, React and Redux for the web. u I currently live in San Francisco and work as Software Engineer at GoDaddy. u Previously, I was Head of Engineering at Tribe App – An early backed Sequoia Capital startup based in Paris and San Francisco.
  3. Summary u Notion of asynchronous programming with Node.js u Guidelines

    for building robust applications u Error Mechanisms in Node.js u Catching, logging, dealing with errors u Express, Async Middleware and Boom ☄
  4. This works try { throw new Error('This is not right!')

    } catch (e) { console.log('We caught the error.') }
  5. But this won’t work try { process.nextTick(() => { throw

    new Error('This is not right!') }) } catch (e) { console.log('We caught the error.') }
  6. Why? Try/Catch is synchronous, process.nextTick is asynchronous This means: •

    The Callback is queued and will throw the error in the next loop • The Catch block is no longer waiting to catch the exception
  7. What are we dealing with? u We cannot try/catch everything

    u We need other mechanisms to handle async errors u A not-well handled error in one single request can crash your entire application
  8. Errors in JavaScript u Error is a built-in object type

    u Error objects are first class citizens u Runtime errors throw an Error object u It has a name, message and stack property u Besides the generic Error constructor, there are seven other core error constructors in JavaScript: EvalError, InternalError, RangeError, RefrenceError, SyntaxError, TypeError, URIError. var notRightError = new Error('This is not right!')
  9. Exceptions in JavaScript u An Exception is what happens when

    you throw something u In JS it’s good practice to throw Error objects u If an exception is unhandled, your process will exit throw new Error('this is the right way') throw 'avoid throwing non-Error stuff'
  10. Errors in Async World u Throwing won’t work u We

    are passing Error objects as callback argument
  11. Avoid patterns like function (err, result) { if (err) {

    // do absolutely nothing } console.log(result) } try { // some code that can potentially throw } catch (err) { // this will never happen } Promise.catch((err) => { // ops, let's just ignore this ha ha }) socket.on('error', (err) => { // again, do nothing ! })
  12. Operational Errors u They are recoverable u Must be expected

    and handled in your application u Examples: Network timeouts, External service is not available or it returns errors, Unexpected or missing user inputs
  13. Programmer Errors u Not recoverable u Do not need to

    be handled u Must be fixed in your application code u Examples: Using a not defined variable, Passing a "string" where an object was expected
  14. Try/Catch var data = "this is not a valid json

    serialized data” try { var parsed = JSON.parse(data) } catch (err) { console.error(err.message) // print: Unexpected token h in JSON at position 1 }
  15. Callbacks function fetchAndParse (url, callback) { function requestGetCallback (err, res,

    body) { if (err) return callback(err) if (res.statusCode > 399) { return callback(new Error(`Server returns ${res.statusCode}`)) } try { callback(null, JSON.parse(body)) } catch (err) { callback(err) } } request.get(url, requestGetCallback) }
  16. Promises function makeRequest (url) { return new Promise((resolve, reject) =>

    { request.get(url, (err, res, body) => { if (err) return reject(err) if (res.statusCode > 399) { return reject(new Error(`Server returns ${res.statusCode}`)) } resolve(body) }) }) } makeRequest('http://example.com/json') .then((body) => JSON.parse(body)) .then((parsedBody) => console.log(parsedBody)) .catch((err) => { /* handle error */ })
  17. Async/Await – wait what? ! try { const body =

    await makeRequest('http://example.com/json') const parsedBody = JSON.parse(body) console.log(parsedBody) } catch (err) { // handle error } u Just syntax sugar for promises u It allows us to use Try/Catch
  18. EventEmitter const server = net.createServer((socket) => { socket.end('Goodbye ! \n')

    }) server.on('error', (err) => { // handle error }) // grab an arbitrary unused port server.listen(() => { console.info(`TCP server listening on ${server.address()}`) })
  19. Example: HTTP Server const server = http.createServer((req, res) => {

    const query = url.parse(request.url, true).query if (!query.uid) { // do not track client errors res.writeHead(400, { 'Content-Type': 'text/plain' }) return res.end('Bad Request') } db.get(`user:${query.uid}`, (err, user) => { if (err) { return trackError(err, () => { res.writeHead(500, { 'Content-Type': 'text/plain' }) res.end('Internal Server Error') }) } if (!user) { res.writeHead(404, { 'Content-Type': 'text/plain' }) return res.end(’Not Found') } res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(`${user.firstname} ${user.lastname}`) }) }) server.listen(8080, (err) => { if (err) throw err // re-throw this and handle it in the global handler console.info('Server is listening on *:8080') }) // basic global error handler process.on('uncaughtException', (err) => { trackError(err, () => process.exit(1)) })
  20. Example: trackError function (1) function trackError (err, callback) { logger.error(err.message,

    { stack: err.stack }) sendTextMessage(phoneNumber, err.message, (sendError) => { if (sendError) { logger.error(sendError.message, { stack: sendError.stack }) } callback() }) }
  21. Example: trackError function (2) const Raven = require('raven') Raven.config('https://<key>:<secret>@sentry.io/<project>').install() function

    trackError (err, callback) { logger.error(err.message, { stack: err.stack }) Raven.captureException(err, (sendErr) => { if (sendErr) { logger.error(sendErr.message, { stack: sendErr.stack }) } }) }
  22. Example: Graceful shutdown function closeServer (cb) { if (!server.address()) return

    cb() server.close(cb) } function closeDBConnection (cb) { if (!db.isConnected()) return cb() db.close(cb) } function shutdownGracefully (cb) { closeServer(() => { closeDBConnection(cb) }) } // global error handler process.on('uncaughtException', (err) => { shutdownGracefully(() => { trackError(err, () => process.exit(1)) }) })
  23. Example: async-middleware.js const boom = require('boom') const asyncMiddleware = fn

    => (req, res, next) => { Promise.resolve(fn(req, res, next)).catch((err) => { if (!err.isBoom) { // generic internal error server return next(boom.badImplementation(err)) } next(err) }) }
  24. Example: error-handler.js const logger = require('winston') const errorHandler = fn

    => (err, req, res, next) => { const shouldLog = (err.isServer || typeof err.isServer === 'undefined') if (shouldLog) { logger.error(err.message, { stack: err.stack, originalUrl: req.originalUrl }) } // handle boom errors if (err.isBoom) { return res.status(err.output.statusCode).json(err.output.payload) } res.status(500).json({ error: { message: 'Unexpected Error' } }) }
  25. Example: app.js (1) const boom = require('boom') const express =

    require('express') const joi = require('joi') const logger = require('winston') const asyncMiddleware = require('./async-middleware') const errorHandler = require('./error-handler') const shutdownGracefully = require('./shutdown-gracefully') const app = express()
  26. Example: app.js (2) const helloSchema = { format: joi.any().required().valid(['json', 'text'])

    } app.get('/hello/:format', asyncMiddleware(async (req, res, next) => { const result = joi.validate(req.params, helloSchema) // validation if (result.error) { throw boom.badRequest(result.error.message) } switch (req.params.format) { case 'json': return res.json({ hello: 'world' }) default: res.send('hello world') } }))
  27. Example: app.js (3) app.use(errorHandler()) app.listen(8080, (err) => { if (err)

    throw err logger.info('App listening on *:8080') }) // global error handler process.on('uncaughtException', (err) => { shutdownGracefully(() => { trackError(err, () => process.exit(1)) }) })
  28. Conclusions u Do not simply log errors, take some actions

    when you can u Programmer errors should NOT be handled u Have a global uncaughtException listener for errors you cannot not handle u Do not centralize handling of operational errors u When delivering errors, use the standard Error class and its standard properties