Asynchronous programming done right - Node.js

70
Asynchronous programming done right. Without race conditions ..ions ..io on ..ns ditions. by Piotr Pelczar (Athlan)

description

Asynchronous programming done right. Without race conditions. Good pratcices in Node.js.

Transcript of Asynchronous programming done right - Node.js

Page 1: Asynchronous programming done right - Node.js

Asynchronousprogramming

done right.

Without race conditions ..ions ..io on..ns ditions.

by Piotr Pelczar (Athlan)

Page 2: Asynchronous programming done right - Node.js
Page 3: Asynchronous programming done right - Node.js

About me

Piotr Pelczar

Freelancer for 8yrs

PHP, Node.js, Java/Groovy

Zend Certified Engineer

IPIJ, Startups

Page 4: Asynchronous programming done right - Node.js

Stay in touch

athlan.pl

[email protected]

/piotr.pelczarfacebook.com

/athlangithub.com

/piotrpelczarslideshare.net

/ppelczarlinkedin.com/in

Page 5: Asynchronous programming done right - Node.js

Asynchronousprogramming

Asynchronous actions are actions executed in a non-blockingscheme, allowing the main program flow to continue processing.

Page 6: Asynchronous programming done right - Node.js

How software lives inhardware?

Operating systems are process based

Each process has assigned processor, registers, memory

Page 7: Asynchronous programming done right - Node.js

How software lives inhardware?

Process paralelism using threads (thread pools)

Switching processor over processes/threads causes contextswitching

Page 8: Asynchronous programming done right - Node.js

1. context switching = wasting time

Page 9: Asynchronous programming done right - Node.js

Sync programmingIn trivial, sequential approach

Each operation is executed sequentially:

O(t) > O(t+1)

if O(t) stucks, O(t+1) waits...

Page 10: Asynchronous programming done right - Node.js

Sync programming

Page 11: Asynchronous programming done right - Node.js

This is cool, software flow is predictibleBut not in high throughput I/O

I/O costs because of waiting time...

Page 12: Asynchronous programming done right - Node.js

High throughput I/OHigh throughput I/O doesn't mean:

Memory operations

Fast single-thread computing

Page 13: Asynchronous programming done right - Node.js

High throughput I/OHigh throughput I/O means:

HTTP requests

Database connections

Queue system dispatching

HDD operations

Page 14: Asynchronous programming done right - Node.js

2. Avoid I/O blocking

Page 15: Asynchronous programming done right - Node.js

2. Avoid I/O blocking

Page 16: Asynchronous programming done right - Node.js

Single-threaded, event loopmodel

Imagine a man, who has a task:

Walk around

When bucket is full of water, just pour another bucket

Go to next bucket

Page 17: Asynchronous programming done right - Node.js

There is no sequencesIn async programming, results appears in no sequences

operation1(); // will output "operation1 finished."operation2(); // will output "operation2 finished."operation3(); // will output "operation3 finished."

Page 18: Asynchronous programming done right - Node.js

There is no sequencesoperation1() would be

var amqp = require("amqp")var eventbus = amqp.createConnection();console.log("AMQP connecting...");

eventbus.on("ready", function() { console.log("AMQP connected...");

callback(); return;});

Page 19: Asynchronous programming done right - Node.js

There is no sequencesoperation2() would be

var redis = require("redis")var conn = redis.createClient(port, host, options);console.log("Redis connecting...");

conn.auth(pass, function(err) { if(err) console.log("Redis failed..."); else console.log("Redis connected..."); callback(); return;});

Page 20: Asynchronous programming done right - Node.js

There is no sequencesoperation3() would be

var mongojs = require("mongojs");

console.log("Mongo connecting...");var conn = mongojs.connect(connectionString); // blocking operationconsole.log("Mongo connected...");

callback();return;

Page 21: Asynchronous programming done right - Node.js

There is no sequencesExpectations?

AMQP connecting... // operation1()AMQP connected... // operation1()Redis connecting... // operation2()Redis failed... // operation2()Mongo connecting... // operation3(), blockingMongo connected... // operation3()

Page 22: Asynchronous programming done right - Node.js

There is no sequencesExpectations?

Page 23: Asynchronous programming done right - Node.js

There is no sequencesThe result:

AMQP connecting... // operation1()Redis connecting... // operation2()Mongo connecting... // operation3(), blockingMongo connected... // operation3()Redis failed... // operation2()AMQP connected... // operation1()

Page 24: Asynchronous programming done right - Node.js

There is no sequences

Page 25: Asynchronous programming done right - Node.js

So... what functionsreturns?

You can perform future tasks in function, so what will bereturned?

value123 will be returned,just after blocking code, without waiting for non-blocking.

function my_function() { operation1(); operation2(); operation3();

return "value123";}

Page 26: Asynchronous programming done right - Node.js

Assume: Functions doesNOT returns values

The function block is executed immedietally from top to bottom.You cannot rely to return value, because it is useless.

Page 27: Asynchronous programming done right - Node.js

CallbacksCallback is the reference to function.

var callbackFunction = function(result) { console.log("Result: %s", result)}

When operation is done, the callback function is executed.callbackFunction("test1") // "Result: test1" will be printed out

Page 28: Asynchronous programming done right - Node.js

CallbacksIf callbackFunction is a variable (value = reference),

so can be passed it via function argument.var callbackFunction = function() { ... }someOtherFunction(callbackFunction);

function someOtherFunction(callback) { callback(); // execute function from argument}

Page 29: Asynchronous programming done right - Node.js

CallbacksFunctions can be defined as anonymous (closures)

function someOtherFunction(callback) { var arg1 = "test"; callback(arg1); // execute function from argument}

someOtherFunction(function(arg1) { console.log('done... %s', arg1);})

Page 30: Asynchronous programming done right - Node.js

Callbacks can be nestedNesting callbacks makes code unreadeable:

var amqp = require('amqp');

var connection = amqp.createConnection();

connection.on('ready', function() { connection.exchange("ex1", function(exchange) { connection.queue('queue1', function(q) { q.bind(exchange, 'r1');

q.subscribe(function(json, headers, info, m) { console.log("msg: " + JSON.stringify(json)); }); }); });});

Page 31: Asynchronous programming done right - Node.js

Callbacks can be nestedNesting callbacks makes code unreadeable:

var amqp = require('amqp');

var connection = amqp.createConnection();

connection.on('ready', function() { connection.exchange("ex1", function(exchange) { connection.queue('queue1', function(q) { q.bind(exchange, 'r1');

q.subscribe(function(json, headers, info, m) { console.log("msg: " + JSON.stringify(json)); table.update(select, data, function() { table.find(select, function(err, rows) { // inserted rows... } }); }); }); });});

Page 32: Asynchronous programming done right - Node.js

Asynchronous control flowsPromise design pattern

Libraries that manages callbacks references

Page 33: Asynchronous programming done right - Node.js

Promise design pattern1. Client fires function that will return result in the future

in the future, so it is a promise

2. Function returns promise object immedietalybefore non-blocking operations

3. Client registers callbacks

4. Callbacks will be fired in the future, when task is done

var resultPromise = loader.loadData(sourceFile)

resultPromise(function success(data) { // this function will be called while operation will succeed}, function error(err) { // on fail})

Page 34: Asynchronous programming done right - Node.js

Promise design pattern1. Create deferred object

2. Return def.promise

3. Call resolve() or reject()

var loadData = function(sourceFile) { var def = deferred() , proc = process.spawn('java', ['-jar', 'loadData.jar', sourceFile]) var commandProcessBuff = null , commandProcessBuffError = null; proc.stdout.on('data', function (data) { commandProcessBuff += data }) proc.stderr.on('data', function (data) { commandProcessBuffError += data })

proc.on('close', function (code) { if(null !== commandProcessBuffError) def.reject(commandProcessBuffError) else def.resolve(commandProcessBuff) }) return def.promise}

Page 35: Asynchronous programming done right - Node.js

Promise design pattern

Page 36: Asynchronous programming done right - Node.js

Async Node.js libraryProvides control flows like:

Sequences (series)

Waterfalls (sequences with parameters passing)

Parallel (with limit)

Some/every conditions

While/until

Queue

Page 37: Asynchronous programming done right - Node.js

Async Node.js librarySeries

Page 38: Asynchronous programming done right - Node.js

Async Node.js librarySeries

async.series([ function(callback) { // operation1 }, function(callback) { // operation2 }, function(callback) { // operation3 }], function() { console.log('all operations done')})

Page 39: Asynchronous programming done right - Node.js

Async Node.js libraryParallel

async.parallel([ function(callback) { // operation1 }, function(callback) { // operation2 }, function(callback) { // operation3 }], function() { console.log('all operations done')})

Page 40: Asynchronous programming done right - Node.js

Async Node.js libraryParallel limit

Page 41: Asynchronous programming done right - Node.js

Async Node.js libraryParallel limit

var tasks = [ function(callback) { // operation1 }, function(callback) { // operation2 }, // ...]

async.parallelLimit(tasks, 2, function() { console.log('all operations done')})

Page 42: Asynchronous programming done right - Node.js

Async Node.js libraryWaterfall

async.waterfall([ function(callback) { // operation1 callback(null, arg1, arg2) }, function(arg1, arg2, callback) { // operation2 callback(null, foo, bar) }, function(foo, bar, callback) { // operation3 }], function() { console.log('all operations done')})

Page 43: Asynchronous programming done right - Node.js

Async Node.js libraryWhilst

async.doWhilst( function(done) { // operation1 done(null, arg1, arg2) }, function() { return pages < limit }], function() { console.log('done')})

Page 44: Asynchronous programming done right - Node.js

Asynchronousprogramming traps

Dealing with callbacks may be tricky. Keep your code clean.

Page 45: Asynchronous programming done right - Node.js

Unnamed callbacksKeep your code clean, don't name callback function callback

function doSomething(callback) { return callback;}

Page 46: Asynchronous programming done right - Node.js

Unnamed callbacksfunction doSomething(callback) { doAnotherThing(function(callback2) { doYetAnotherThing(function(callback3) { return callback(); }) })}

Page 47: Asynchronous programming done right - Node.js

Unnamed callbacksInstead of this, name your callbacks

function doSomething(done) { doAnotherThing(function(doneFetchingFromApi) { doYetAnotherThing(function(doneWritingToDatabase) { return done(); }) })}

Page 48: Asynchronous programming done right - Node.js

Double callbacksfunction doSomething(done) {

doAnotherThing(function (err) { if (err) done(err); done(null, result); }); }

Callback is fired twice!

Page 49: Asynchronous programming done right - Node.js

Double callbacksFix: Always prepend callback execution with return statement.

function doSomething(done) {

doAnotherThing(function (err) { if (err) return done(err); return done(null, result); });}

Normally, return ends function execution, why do not keep thisrule while async.

Page 50: Asynchronous programming done right - Node.js

Double callbacksDouble callbacks are very hard to debug.

The callback wrapper can be written and execute it only once.setTimeout(function() { done('a')}, 200)setTimeout(function() { done('b')}, 500)

Page 51: Asynchronous programming done right - Node.js

Double callbacksvar CallbackOnce = function(callback) { this.isFired = false this.callback = callback} CallbackOnce.prototype.create = function() { var delegate = this return function() { if(delegate.isFired) return delegate.isFired = true delegate.callback.apply(null, arguments) }}

Page 52: Asynchronous programming done right - Node.js

Double callbacksobj1 = new CallbackOnce(done)

// decorate callbacksafeDone = obj1.create() // safeDone() is proxy function that passes arguments setTimeout(function() { safeDone('a') // safe now...}, 200)setTimeout(function() { safeDone('b') // safe now...}, 500)

Page 53: Asynchronous programming done right - Node.js

Unexpected callbacksNever fire callback until task is done.

function doSomething(done) {

doAnotherThing(function () { if (condition) { var result = null // prepare result... return done(result); } return done(null); });}

The ending return will be fired even if condition pass.

Page 54: Asynchronous programming done right - Node.js

Unexpected callbacksNever fire callback until task is done.

function doSomething(done) {

doAnotherThing(function () { if (condition) { var result = null // prepare result... return done(result); } else { return done(null); } });}

Page 55: Asynchronous programming done right - Node.js

Unexpected callbacksNever use callback in try clause!

function (callback) { another_function(function (err, some_data) { if (err) return callback(err); try { callback(null, JSON.parse(some_data)); // error here } catch(err) { callback(new Error(some_data + ' is not a valid JSON')); } });}

If callback throws an exception, then it is executed exactly twice!

Page 56: Asynchronous programming done right - Node.js

Unexpected callbacksNever use callback in try clause!

function (callback) { another_function(function (err, some_data) { if (err) return callback(err); try { var parsed = JSON.parse(some_data) } catch(err) { return callback(new Error(some_data + ' is not a valid JSON')); } callback(null, parsed); });}

Page 57: Asynchronous programming done right - Node.js

Unexpected callbacksNever use callback in try clause!

Page 58: Asynchronous programming done right - Node.js

Take care of eventsRead docs carefully. Really.

function doSomething(done) {

var proc = process.spawn('java', ['-jar', 'loadData.jar', sourceFile]) var procBuff = ''; proc.stdout.on('data', function (data) { procBuff += data; }); // WAT?! proc.stderr.on('data', function (data) { done(new Error("An error occured: " + data)) }); proc.on('close', function (code) { done(null, procBuff); }}

Page 59: Asynchronous programming done right - Node.js

Take care of eventsRead docs carefully. Really.

function doSomething(done) {

var proc = process.spawn('java', ['-jar', 'loadData.jar', sourceFile]) var procBuff = ''; var procBuffError = '';

proc.stdout.on('data', function (data) { procBuff += data; });

proc.stderr.on('data', function (data) { proc += data; });

proc.on('close', function (code) { if(code !== 0) { return done(new Error("An error occured: " + procBuffError)); } else { return done(null, procBuff) } }

}

Page 60: Asynchronous programming done right - Node.js

Unreadable and logsKeep in mind, that asynchronous logs will interweave

There are not sequenced

Or there will be same log strings

Page 61: Asynchronous programming done right - Node.js

Unexpected callbacksAsynchronous logs will interweave

Page 62: Asynchronous programming done right - Node.js

Unreadable and logsLogs without use context are useless...

function getResults(keyword, done) { http.request(url, function(response) { console.log('Fetching from API') response.on('error', function(err) { console.log('API error') }) });}

Page 63: Asynchronous programming done right - Node.js

Unreadable and logsfunction getResults(keyword, done) { var logContext = { keyword: keyword } http.request(url, function(response) { console.log(logContext, 'Fetching from API') response.on('error', function(err) { console.log(logContext, 'API error') }) });}

Page 64: Asynchronous programming done right - Node.js

Unreadable and logsCentralize your logs - use logstash

And make them searcheable - Elasticsearch + Kibana

Page 65: Asynchronous programming done right - Node.js

Too many openedbackground-tasks

While running parallel in order to satisfy first-better algorithm,others should be aborted

Page 66: Asynchronous programming done right - Node.js

Too many openedbackground-tasks

Provide cancellation API:var events = require('events')

function getResults(keyword) { var def = deferred() var eventbus = new events.EventEmitter() var req = http.request(url, function(response) { var err = null , content = null res.on('data', function(chunk) { content += chunk; }); response.on('close', function() { if(err) return def.reject(err) else return def.resolve(content) }) response.on('error', function(err) { err += err }) });

Page 67: Asynchronous programming done right - Node.js

Too many openedbackground-tasks

Provide cancellation API:var response = getResults('test')

response.result(function success() { // ...}, function error() { // ...})

// if we needresponse.events.emit('abort')

Page 68: Asynchronous programming done right - Node.js

Everything runs in parallel except your code.

When currently code is running, (not waiting for I/O descriptors)whole event loop is blocked.

Page 69: Asynchronous programming done right - Node.js

THE ENDby Piotr Pelczar

Page 70: Asynchronous programming done right - Node.js

Q&A

by Piotr Pelczar