16. Single-threaded, event loop
model
Imagine a man, who has a task:
Walk around
When bucket is full of water, just pour another bucket
Go to next bucket
17. There is no sequences
In async programming, results appears in no sequences
operation1(); // will output "operation1 finished."
operation2(); // will output "operation2 finished."
operation3(); // will output "operation3 finished."
18. There is no sequences
operation1() would be
var amqp = require("amqp")
var eventbus = amqp.createConnection();
console.log("AMQP connecting...");
eventbus.on("ready", function() {
console.log("AMQP connected...");
callback();
return;
});
19. There is no sequences
operation2() 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;
});
20. There is no sequences
operation3() would be
var mongojs = require("mongojs");
console.log("Mongo connecting...");
var conn = mongojs.connect(connectionString); // blocking operation
console.log("Mongo connected...");
callback();
return;
25. So... what functions
returns?
You can perform future tasks in function, so what will be
returned?
value123 will be returned,
function my_function() {
operation1();
operation2();
operation3();
return "value123";
}
just after blocking code, without waiting for non-blocking.
26. Assume: Functions does
NOT returns values
The function block is executed immedietally from top to bottom.
You cannot rely to return value, because it is useless.
27. Callbacks
Callback 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
28. Callbacks
If 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
}
29. Callbacks
Functions 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);
})
30. Callbacks can be nested
Nesting 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));
});
});
});
});
31. Callbacks can be nested
Nesting 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...
}
});
});
});
});
});
33. Promise design pattern
1. Client fires function that will return result in the future
in the future, so it is a promise
2. Function returns promise object immedietaly
before 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
})
34. Promise design pattern
1. 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
}
47. Unnamed callbacks
Instead of this, name your callbacks
function doSomething(done) {
doAnotherThing(function(doneFetchingFromApi) {
doYetAnotherThing(function(doneWritingToDatabase) {
return done();
})
})
}
48. Double callbacks
function doSomething(done) {
doAnotherThing(function (err) {
if (err) done(err);
done(null, result);
});
}
Callback is fired twice!
49. Double callbacks
Fix: 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 this
rule while async.
50. Double callbacks
Double 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)
52. Double callbacks
obj1 = new CallbackOnce(done)
// decorate callback
safeDone = obj1.create() // safeDone() is proxy function that passes arguments
setTimeout(function() {
safeDone('a') // safe now...
}, 200)
setTimeout(function() {
safeDone('b') // safe now...
}, 500)
53. Unexpected callbacks
Never 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.
54. Unexpected callbacks
Never 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);
}
});
}
55. Unexpected callbacks
Never 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!
56. Unexpected callbacks
Never 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);
});
}
62. Unreadable and logs
Logs 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')
})
});
}
63. Unreadable and logs
function 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')
})
});
}
64. Unreadable and logs
Centralize your logs - use logstash
And make them searcheable - Elasticsearch + Kibana
65. Too many opened
background-tasks
While running parallel in order to satisfy first-better algorithm,
others should be aborted
66. Too many opened
background-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
})
});
67. Too many opened
background-tasks
Provide cancellation API:
var response = getResults('test')
response.result(function success() {
// ...
}, function error() {
// ...
})
// if we need
response.events.emit('abort')
68. Everything runs in parallel except your code.
When currently code is running, (not waiting for I/O descriptors)
whole event loop is blocked.