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
### 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
## Process in Node JS * 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) ```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") }); ```
## Database 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 sequelize.js In reality, just use a 3rd party library like sequelize.js ```javascript const { Sequelize } = require('sequelize'); // Option 1: Passing a connection URI const sequelize = new Sequelize('sqlite::memory:') // Example for sqlite const sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname') // Example for postgres // Option 2: Passing parameters separately (sqlite) const sequelize = new Sequelize({ dialect: 'sqlite', storage: 'path/to/database.sqlite' }); // Option 3: Passing parameters separately (other dialects) const sequelize = new Sequelize('database', 'username', 'password', { host: 'localhost', dialect: /* one of 'mysql' | 'mariadb' | 'postgres' | 'mssql' */ }); // Testing the connection try { await sequelize.authenticate(); console.log('Connection has been established successfully.'); } catch (error) { console.error('Unable to connect to the database:', error); } ```
## 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 will work with try/catch as expected
## 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.'); } ```
## 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'); }); ```
## Best practice * Follow **D**o **N**ot **R**epeat (DRY) approach
## 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) ```