footer: Β© NodeProgram.com, 2016 slidenumbers: true
https://github.com/azat-co/node-patterns
git clone https://github.com/azat-co/node-patterns
Azat Mardan
Twitter: @azat_co Email: [email protected] Blog: webapplog.com
- Work: Technology Fellow at Capital One
- Experience: FDIC, NIH, DocuSign, HackReactor and Storify
- Books: Practical Node.js, Pro Express.js, Express.js API and 8 others
- Teach: NodeProgram.com
FREE: 7+ hours of videos
- JavaScript
- Asynchronous + Event Driven
- Non-Blocking I/O
- Async code is hard
- Code complexity grows exponentially
- Good code organization is important
How to schedule something in the future?
Functions are First-Class Citizens
var t = function(){...}
setTimeout(t, 1000)
t is a callback
var fs = require('fs')
var callback = function(error, data){...}
fs.readFile('data.csv', 'utf-8', callback)
error
1st argument, null if everything is okaydata
is the second argumentcallback
is the last argument
Naming doesn't matter but order matters.
Node.js won't enforce the arguments.
Convention is not a guarantee. It's just a style. β Read documentation or source code.
How to ensure the right sequence? Control flow π
HTTP requests to:
- Get an auth token
- Fetch data
- PUT an update
They must be executed in a certain order.
... // callback is defined, callOne, callTwo, and callThree are defined
callOne({...}, function(error, data1) {
if (error) return callback(error, null)
// work to parse data1 to get auth token
// fetch the data from the API
callTwo(data1, function(error, data2) {
if (error) return callback(error, null)
// data2 is the response, transform it and make PUT call
callThree(data2, function(error, data3) {
//
if (error) return callback(error, null)
// parse the response
callback(null, data3)
})
})
})
fs.readdir(source, function (err, files) {
if (err) {
console.log('Error finding files: ' + err)
} else {
files.forEach(function (filename, fileIndex) {
console.log(filename)
gm(source + filename).size(function (err, values) {
if (err) {
console.log('Error identifying file size: ' + err)
} else {
console.log(filename + ' : ' + values)
aspect = (values.width / values.height)
widths.forEach(function (width, widthIndex) {
height = Math.round(width / aspect)
console.log('resizing ' + filename + 'to ' + height + 'x' + height)
this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
if (err) console.log('Error writing file: ' + err)
})
}.bind(this))
}
})
})
}
}
- Hard to read
- Hard to modify/maintain/enhance
- Easy for devs to make bugs
- Closing parens - πΏ
- Abstract into named functions (hoisted or variables)
- Use obververs
- Use advanced libraries and techniques
callOne({...}, processResponse1)
function processResponse1(error, data1) {
callTwo(data1, processResponse2)
}
function processResponse2(error, data2) {
callThere(data2, processResponse3)
}
function processResponse3(error, data1) {
...
}
var processResponse1 = require('./response1.js')
callOne({...}, processResponse1)
// response1.js
var processResponse2 = require('./response2.js')
module.exports = function processResponse1(error, data1) {
callTwo(data1, processResponse2)
}
// response2.js
var processResponse3 = require('./response3.js')
module.exports = function processResponse2(error, data2) {
callThere(data2, processResponse3)
}
// response3.js
module.exports = function processResponse3(error, data3) {
...
}
How to modularize code properly?
module.exports = {...}
module.exports.obj = {...}
exports.obj = {...}
Note: exports = {...}
is anti-pattern.
How to modularize dynamic code or where to initialize?
module.exports = function(options) {...}
module.exports.func = function(options) {...}
exports.func = function(options) {...}
// code A
module.exports = function(options){
// code B
}
When you require
, code A is run and code B is not.
Code A is run only once, no matter how many times you require
.
You need to invoke the object to run code B.
node import-main
// main.js
var routes = require('./routes')
// routes/index.js
module.exports = {
users: require('./users.js'),
accounts: require('./accounts.js')
...
}
require
: modules are cached
// module.js
var a = 1 // Private
module.exports = {
b: 2 // Public
}
// program.js
var m = require('./module')
console.log(m.a) // undefined
console.log(m.b) // 2
m.b ++
require('./main')
// main.js
var m = require('./module')
console.log(m.b) // 3
node main.js
node program.js
Modules are cached on based on their resolved filename.
Filename will break the caching
var m = require('./MODULE')
var m = require('./module')
Or different paths
global
global.name
or
GLOBAL.name
_log = global.console.log
global.console.log = function(){
var args = arguments
args[0] = '\033[31m' +args[0] + '\x1b[0m'
return _log.apply(null, args)
}
global is powerful... anti-pattern
similar window.jQuery = jQuery
use it sparringly
^with a lot of power comes a lot of responsibility
How to organize your modular code into classes?
(At least in ES5)
Objects inherit from other objects
Functions are objects too.
module.exports = function(options) {
// initialize
return {
getUsers: function() {...},
findUserById: function(){...},
limit: options.limit || 10,
// ...
}
}
require('util').inherits(child, parent)
Middleware pattern is a series of processing units connected together, where the output of one unit is the input for the next one. In Node.js, this often means a series of functions in the form:
function(args, next) {
// ... Run some code
next(output) // Error or real output
}
Request is coming from a client and response is sent back to the client.
request->middleware1->middleware2->...middlewareN->route->response
app.use(function(request, response, next) {
// ...
next()
}, function(request, response, next) {
next()
}, function(request, response, next) {
next()
})
Callbacks are still hard to manage even in modules!
- Module Job is performing a task.
- In the main file, we import Job.
How do we specify a callback (some future logic) on the Job's task completion?
Maybe:
var job = require('./job.js')(callback)
What about multiple callbacks?
Not very scalable π’
Observer pattern with event emitters!
// module.js
var util = require('util')
var Job = function Job() {
// ...
this.process = function() {
// ...
job.emit('done', { completedOn: new Date() })
}
}
util.inherits(Job, require('events').EventEmitter)
module.exports = Job
^ module
// main.js
var Job = require('./module.js')
var job = new Job()
job.on('done', function(details){
console.log('Job was completed at', details.completedOn)
job.removeAllListeners()
})
job.process()
^main
emitter.listeners(eventName)
emitter.on(eventName, listener)
emitter.once(eventName, listener)
emitter.removeListener(eventName, listener)
// server.js
var app = express()
app.set(port, 3000)
...
app.use(logger('dev'))
...
var boot = require('./routes')(app)
boot({...}, function(){...})
// routes/index.js
module.exports = function(app){
return function(options, callback) {
app.listen(app.get('port'), options, callback)
}
}
async
andneo-async
- Promises - not really helping much
- Generators - promising
- Async await - nice wrapper for promises
- Callbacks
- Observer
- Singleton
- Plugins
- Middleware
- Bunch of other stuff π₯
I know it's been a lot π Event Emitters, modules and callbacks are at the core of Node. Know thy patterns!
Scale 1-10 (10 is highest)
Anyone below 8?
This is your chance ask a question to make it 10!
Q&A βπ π
Send bugs π to
https://github.com/azat-co/node-patterns/issues
Twitter: @azat_co Email: [email protected]