Slide 1

Slide 1 text

Understanding Error Handling in Node.js Jacopo Daeli Lead Software Engineer @ GoDaddy San Francisco, Feb 15th 2018 Holberton School

Slide 2

Slide 2 text

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.

Slide 3

Slide 3 text

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 ☄

Slide 4

Slide 4 text

Notion of asynchronous programming with Node.js

Slide 5

Slide 5 text

This works try { throw new Error('This is not right!') } catch (e) { console.log('We caught the error.') }

Slide 6

Slide 6 text

But this won’t work try { process.nextTick(() => { throw new Error('This is not right!') }) } catch (e) { console.log('We caught the error.') }

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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!')

Slide 10

Slide 10 text

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'

Slide 11

Slide 11 text

Errors in Async World u Throwing won’t work u We are passing Error objects as callback argument

Slide 12

Slide 12 text

Guidelines for building robust applications Two rules to rule them all!

Slide 13

Slide 13 text

Understand when and why your errors happen

Slide 14

Slide 14 text

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 ! })

Slide 15

Slide 15 text

Understand Operational Errors vs Programmer Errors and learn how to deal with these

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

Error Mechanisms in Node.js

Slide 19

Slide 19 text

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 }

Slide 20

Slide 20 text

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) }

Slide 21

Slide 21 text

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 */ })

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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()}`) })

Slide 24

Slide 24 text

Catching, logging, dealing with errors

Slide 25

Slide 25 text

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)) })

Slide 26

Slide 26 text

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() }) }

Slide 27

Slide 27 text

Example: trackError function (2) const Raven = require('raven') Raven.config('https://:@sentry.io/').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 }) } }) }

Slide 28

Slide 28 text

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)) }) })

Slide 29

Slide 29 text

Express, Async Middleware and Boom

Slide 30

Slide 30 text

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) }) }

Slide 31

Slide 31 text

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' } }) }

Slide 32

Slide 32 text

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()

Slide 33

Slide 33 text

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') } }))

Slide 34

Slide 34 text

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)) }) })

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

Further Readings u https://nodejs.org/api/errors.html u https://www.joyent.com/node-js/production/design/errors u https://blog.risingstack.com/node-js-production-checklist