## You Don't Know Node.js (or, How JavaScript Actually Works) Vance Lucas

Vance Lucas

Personal



Professional

Family
## Two Parts 1. The Event Loop (aka "How JavaScript Actually Works") 2. Node.js Patterns and Pitfalls
## The Event Loop How JavaScript Actually Works
Node.js Event Loop https://www.packtpub.com/web-development/build-network-application-node-video
## How The Event Loop Works * http://latentflip.com/loupe/ * Philip Roberts: What the heck is the event loop anyway? https://www.youtube.com/watch?v=8aGhZQkoFbQ
JS Environment
# Demo
### Don't Block The Event Loop! * Don’t do heavy I/O, large file parsing, or major computations in your main process * All major processing in compiled libraries or queue * node-blocked NPM package * https://github.com/tj/node-blocked
# Node.js
## Node Process * Runs forever * Does not cleanup/tear down * Need to watch memory usage * Callbacks for everything * Single threaded (*)
## Callbacks in JS ``` $.get('http://example.com', function (data) { console.log(data); }); ```
## Callbacks in Node ``` var fs = require('fs'); fs.readFile('myfile.txt', function (err, data) { if (err) { console.error(err); } console.log('Got data: ', data); }); ```
## Everything is async ``` db.users.find({ name: 'John Connor' }, function (err, users) { if (err) { res.json({ success: false, message: 'Error: unable to find John Connor' }); } let addresses = users.map((user) => user.address); res.json({ success: true, addresses }); }); ```
## Promises (for those who don't like callbaks)
## Promises ```javascript doSomething() .then(doThing2) .then(doThing3) .then(doThing4) .then(function (result) { doThing5(someArg, result); }) .catch(function (e) { console.error("unable to read whatever") }); ```
## Parallel Exection ```javascript Promise.all([ doThing2, doThing3, doThing4 ]) .then(function (results) { doThing5(someArg, results); }) .catch(console.error); ```
## Callback Style ```javascript fs.readFile("file.json", function (err, json) { if (err) { console.error("unable to read file"); } else { try { json = JSON.parse(json); console.log(json.success); } catch(e) { console.error("invalid json in file"); } } }); ```
## Promises ```javascript fs.readFileAsync("file.json") .then(JSON.parse) .then(console.log) .catch(SyntaxError, function(e) { console.error("Invalid json in file"); }) .catch(function(e) { console.error("Unable to read file") }); ```
# Databases
## DB Connections * How are you connecting to your db? * Are you using a connection pool? * Remember: Node runs forever, and does not cleanup after each request
## Before Typical style of PHP, Ruby, Python, etc. ```javascript const options = require('../config'); const mysql = require('mysql'); // Single db connection const connection = mysql.createConnection(options.mysql); function getSettings(callback) { connection.query( 'SELECT id, setting_name, setting_value FROM settings', callback ); } ```
## After Use connection pooling since Node.js runs forever ```javascript const options = require('../config'); // ... const mysqlPool = require('./mysqlPool'); // Connection pool let connection = mysqlPool.create({ logSql: false }, "DBPool", options.mysql); function getSettings(callback) { connection.query( 'SELECT id, setting_name, setting_value FROM settings', callback ); } ```
## Use Knex.js In reality, just use a 3rd party library like Knex.js ```javascript const knex = require('knex'); // Does connection pooling for you const connection = require('knex')({ client: 'pg', connection: process.env.DATABASE_URL }); // Use Knex.js and Promises function getSettings() { return connection.select('id', 'setting_name', 'setting_value') .from('settings'); } ```
## Error Handling
## On Unhandled Error... * Node process crashes & exits * Need to catch and handle all errors * Try/catch doesn’t work like you think * Promises can help, but also have downsides
## Uncaught Exceptions ```javascript process.on('uncaughtException', function (err) { console.error('Uncaught exception: ', err); process.exit(1); }); ```
## Unhandled Promise Rejections ```javascript process.on('unhandledRejection', function (err, promise) { console.error('Unhandled Promise Rejection: ', err, promise); process.exit(1); }); ```
## Naïve Try/Catch ```javascript const fs = require('fs'); try { fs.readFile('doesnotexist.txt', function (err, data) { throw new Error('File does not exist!'); }); } catch(e) { console.log('There was an error, but I handled it, right?'); } ```
## Try/Catch Blocks * Callbacks will be executed on the stack at a different time than your try/catch block * They are basically useless unless they are **everywhere** in all your async callbacks * Promises can help with 'catch' method * async/await (future) will work with try/catch as expected
## Future with async/await ```javascript const fs = require('fs'); try { let file = await fs.readFile('doesnotexist.txt'); } catch(e) { console.log('There was an error, but I actually handled it this time.'); } ```
## Gotchas ### Things to watch out for
## Network I/O * No default timeouts for network requests * Sometimes you still have to .end() the connection itself on timeout event * This is on purpose, **by design**
## Adding Timeouts ```javascript server.on('connection', function (socket) { console.log('SOCKET OPENED'); socket.on('end', function() { log.info('SOCKET END: other end of the socket sends a FIN packet'); }); // Set & listen for timeout socket.setTimeout(2000); socket.on('timeout', function () { log.info('SOCKET TIMEOUT'); socket.end(); // have to sever/end socket manually }); }); ```
## Express.js Routes Sends a response ```javascript app.get('/ping', function (req, res) { res.send('Pong'); }); ``` Will not send a response ```javascript app.get('/ping2', function (req, res) { console.log('Never ends HTTP request'); }); ```
## Keep it DRY
## Response Logic ```javascript app.post('/register', jsonApi.isType('user'), function (req, res) { users.register(req.body.data.attributes.first_name) .then(function (result) { res.json({ data: result }); }) .catch(function (err) { res.send('Error! ' + err); }); }); ``` ```javascript app.get('/:id', function (req, res) { users.findById(req.params.id) .then(function (result) { res.json({ data: result }); }) .catch(function (err) { res.send('Error! ' + err); }); }); ```
## Logic-less routes Use functions that return functions ```javascript app.post('/register', jsonApi.isType('user'), function (req, res) { users.register(req.body.data.attributes.first_name) .then(sdk.respondWithResults(req, res, 201)) .catch(sdk.respondWithError(req, res)); }); ``` Pass in `req` and `res` ```javascript app.get('/:id', function (req, res) { users.findById(req.params.id) .then(sdk.respondWithResults(req, res)) .catch(sdk.respondWithError(req, res)); }); ``` `respondWithResults` returns a closure
## Passing req/res Return closure that uses `results` ```javascript function respondWithResults(req, res, type = 'result', httpStatus = 200) { return function (results) { if (!results) { results = {}; } res.status(httpStatus).json(results); }; } ```

setTimeout(callback, 2000)

## Questions? Tomorrow: "Effective Browser JavaScript Debugging" @ 10:00