## You Don't Know Node.js
(or, How JavaScript Actually Works)
Vance Lucas
## Two Parts
1. The Event Loop (aka "How JavaScript Actually Works")
2. Node.js Patterns and Pitfalls
## The Event Loop
How JavaScript Actually Works
## 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
### 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 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")
});
```
## 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');
}
```
## 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');
});
```
## 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