Slide 1

Slide 1 text

You Don’t Know Node.js Explanations for common foot-guns and gotchas Vance Lucas http://vancelucas.com

Slide 2

Slide 2 text

Who are You? • Vance Lucas • http://vancelucas.com • Twitter: @vlucas • Co-founded JavaScript User Group • Node.js based startup postnix.com

Slide 3

Slide 3 text

What is Node? • JavaScript on the server • V8 engine (Google Chrome team) • Fully async non-blocking I/O • No-frills vanilla JS - not a framework

Slide 4

Slide 4 text

Node Process • Runs forever • Does not cleanup/tear down • Need to watch memory usage • Callbacks for everything • Single threaded?* * or is it?

Slide 5

Slide 5 text

Callbacks in JS $.get('http://nodejs.org', function(data) { console.log(data); });

Slide 6

Slide 6 text

Callbacks in Node var fs = require('fs'); fs.readFile('myfile.txt', function(err, data) { console.log(data); });

Slide 7

Slide 7 text

Everything is async db.users.find({ name: 'John' }, function(err, users) { users.forEach(function(user) { console.log(user); }); });

Slide 8

Slide 8 text

The Event Loop Brief overview

Slide 9

Slide 9 text

https://www.packtpub.com/web-development/build-network-application-node-video

Slide 10

Slide 10 text

How Event Loop Works • http://latentflip.com/loupe/ • Philip Roberts: What the heck is the event loop anyway? • https://www.youtube.com/watch?v=8aGhZQkoFbQ

Slide 11

Slide 11 text

Demo

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

Don’t Block The Event Loop! • Don’t do heavy I/O, large file parsing, or computation in your main process • All major processing in queue • node-blocked NPM package • https://github.com/tj/node-blocked

Slide 14

Slide 14 text

Database • How are you connecting to your db? • Are you using a connection pool? • Remember: Node runs forever, and does not cleanup after each request

Slide 15

Slide 15 text

Before var options = require('../config') // ... , mysql = require('mysql') , connection = mysql.createConnection(options.mysql); connection.query('SELECT id, setting_name, setting_value FROM settings', function(err, results) { // ... });

Slide 16

Slide 16 text

After var options = require('../config') // ... , mysqlPool = require('./mysqlPool') // Use MySQL pool for queries var connection = mysqlPool.create({ logSql: false }, "DBPool", options.mysql); connection.query('SELECT id, setting_name, setting_value FROM settings', function(err, results) { // ... }); https://github.com/felixge/node-mysql/issues/778#issuecomment-41077643

Slide 17

Slide 17 text

Streams • Idiomatic node • Use streams to copy file contents - don’t read it into memory • Gulp.js vs. Grunt.js • NPM ‘multipipe’ package • https://github.com/juliangruber/multipipe

Slide 18

Slide 18 text

File Copy Stream var fs = require('fs'); var zlib = require('zlib'); fs.createReadStream('input.txt.gz') .pipe(zlib.createGunzip()) .pipe(fs.createWriteStream('output.txt'));

Slide 19

Slide 19 text

Error Handling • Node process crashes & exits on error • Need to catch and handle all errors • Try/catch doesn’t work like you think • Promises can help

Slide 20

Slide 20 text

Uncaught Exceptions process.on('uncaughtException', function(err){ log.critical('uncaught', err); setTimeout(function(){ // cleanup and exit... }, 1000); });

Slide 21

Slide 21 text

Naive Try/Catch var 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 it\'s cool, because I handled it, right?'); }

Slide 22

Slide 22 text

Try/Catch Blocks • Callbacks will be executed in a different stack than your try/catch block • They are basically useless unless they are everywhere

Slide 23

Slide 23 text

Promises • More elegant code • Can chain calls together • Better error handling

Slide 24

Slide 24 text

Promises doSomething() .then(doThing2()) .then(doThing3()) .then(doThing4()) .then(function(result) { doThing5(someArg, result); }) .catch(function(e) { console.error("unable to read whatever") });

Slide 25

Slide 25 text

Parallel Promise.all([ doThing2(), doThing3(), doThing4() ]) .then(function(results) { doThing5(someArg, results); }) .catch(function(e) { console.error("unable to read whatever") });

Slide 26

Slide 26 text

Callback Style 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"); } } });

Slide 27

Slide 27 text

Promises var Promise = require('bluebird'); var fs = Promise.promisifyAll(require("fs")); fs.readFileAsync("file.json") .then(JSON.parse) .then(function(json) { console.log(json.success); }) .catch(SyntaxError, function(e) { console.error("invalid json in file"); }) .catch(function(e) { console.error("unable to read file") });

Slide 28

Slide 28 text

Promises ns.mxLookup = function(domain) { return ns.mxLookupLocal(domain) .then(function(mxRecords) { if (!mxRecords) { return ns.mxLookupRemote(domain); } return mxRecords; }); }; Code from postnix.com

Slide 29

Slide 29 text

ns.checkSingleEmail = function(user, email, tag, ip) { return new Promise(function(fulfill, reject) { // First, see if user can check an email return ns.canUserCheckEmail(user, ip) .then(function() { // Local db lookup for cached result return ns.localEmailLookup(user, email, tag, ip); }, function(err) { // Error (no credits or restricted by IP) reject(err); }) .then(function(json) { if (json) { // Return cached JSON response immediately return fulfill(json); } // Perform all lookups return ns.mxLookup(domain) .then(function(mxRecords) { if (!mxRecords) { throw new Error('no MX records exist for domain'); } return ns.smtpCheck(user, email, mxRecords); }) .then(function(json) { fulfill(json); }); }) .catch(function(err) { if(err.code == 'ECONNREFUSED') { err.json = ns.errorJson(email, 'connection to mail server refused'); } else { err.json = ns.errorJson(email, err.message || 'connection timeout'); } db.query("DELETE FROM email_mx WHERE domain = ?", [domain]); reject(err); }); }); };

Slide 30

Slide 30 text

Gotchas Things to watch out for

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

Adding Timeouts server.on('connection', function(socket) { log.info('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 }); });

Slide 33

Slide 33 text

setTimeout(callback, 2000)

Slide 34

Slide 34 text

ES6 Makes Life Better

Slide 35

Slide 35 text

ES6 Features • https://github.com/lukehoban/es6features • Use io.js! (node fork w/ES6) • Generators, • Workers in io.js (experimental) • https://github.com/iojs/io.js/pull/1159

Slide 36

Slide 36 text

More Resources • Mixu’s Node Book • http://book.mixu.net/node/ • Event Loop Overview from Mozilla • https://developer.mozilla.org/en-US/docs/Web/ JavaScript/EventLoop

Slide 37

Slide 37 text

Questions? @vlucas | [email protected]