Async
JaxNode October 2018
Happy Birthday!!!
JaxNode 5th year!
Concurrency is Hard!
Concurrency should be as easy as riding a bicycle
Why is concurrency important?
State of Computing today
- Processors aren't getting faster
- Still adding more transistors to piece of silicon
- More cores being added to processors
- Speed is coming from being able to do many things concurrently
Ryan Dahl
Introduced Node.js in 2009
setTimeout(function() {
console.log('World!');
}, 2000);
console.log('Hello ');
Intro
- How Node handles Async work
- Error first callbacks
- Promises
- Generators
- Async/Await
- Async module
JavaScript
- Single Threaded
- Non-blocking
- Callbacks
* https://www.codementor.io/theresamostert/understanding-non-blocking-i-o-in-javascript-cvmg1hp6l
Async API
- When Node starts, it starts Event Loop
- LibUV concurrency library part of Node
- Event Loop looks at timers first
- Then handles event callbacks
- Enters idle phase, internal only
- Then handles I/O callbacks queue
- close callbacks
Timers
- setImmediate(fn)
- setTimeout(fn, milliseconds)
- setInterval(fn, milliseconds)
- process.nextTick(fn)
Demo 1
Error First Callbacks
- Instead of returning a value, functions will expect parameter takes a another function
- The function or lambda will contain an error parameter, and a results parameter
- function query('Arg', function (err, data) {...})
Error first callbacks
function callFn(err, result) {
if (err) console.error(err);
console.log(result);
}
function makeDatabaseQuery(params, cb) {
// ...Do your magic
const result = magicIsCompleted();
if (error) {
cb(error, null);
} else {
cb(null, result);
}
}
makeDatabaseQuery(['12', false, 'Larry'], callFn);
Demo 2
Callback Hell
- JavaScript allows inline functions or lambda
- Developers can wind up having massive trees of callbacks inside one method
- Hard to read and unintuitive
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))
}
})
})
}
})
Streamline Callbacks
- Inline functions are hard to debug
- if you use inline functions, consider naming
- Define separate function and use for the callback
fs.readdir(source, handleFiles);
function handleFiles(err, files) {
if (err) {
console.log('Error finding files: ' + err)
} else {
files.forEach(forEachFile)
}
}
function forEachFile(filename, fileIndex) {
console.log(filename)
gm(source + filename).size(handleSize)
}
function handleSize(err, values) {
if (err) {
console.log('Error identifying file size: ' + err)
} else {
console.log(filename + ' : ' + values)
aspect = (values.width / values.height)
widths.forEach(handleWidth.bind(this))
}
}
function handleWidth(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)
})
}
Generators
- Generates values when needed
- '*' used after 'function' keyword
- Use the 'yield' keyword to return values
- generator results.next().value
- Typically used for iterators
function* simpleGenerator() {
yield 1;
yield 5;
}
const g = simpleGenerator();
const v1 = g.next().value; // --> 1
const v2 = g.next().value; // --> 5
const v3 = g.next().value; // --> undefined
function* myGenerator() {
let i = 0;
while(i < 2) {
i += 1;
yield i;
}
}
const g = myGenerator();
const v1 = g.next(); // --> { value: 1, done: false }
const v2 = g.next(); // --> { value: 2, done: false }
const v3 = g.next(); // --> { value: undefined, done: true }
function* withReturn() {
yield 1;
yield 55;
return 250;
yield 500;
}
const g = withReturn();
const v1 = g.next().value; // --> 1
const v2 = g.next().value; // --> 55
const v3 = g.next().value; // --> 250
const v4 = g.next().value; // --> undefined
Demo 3
Promises
- A type of Monad
- Built in type in Node and JavaScript
- Can chain and nest Promises
- Handling functions with .then and .catch
- Avoids callback Hell, but can still be tricky
const promise = new Promise(function(resolve, reject) {
// do a thing, possibly async, then…
if (/* everything turned out fine */) {
resolve("Stuff worked!");
}
else {
reject(Error("It broke"));
}
});
promise.then(result => {
console.log(result);
}).catch(err => {
console.error(err);
});
const promise = new Promise(function(resolve, reject) {
resolve(1);
});
promise.then(function(val) {
console.log(val); // 1
return val + 2;
}).then(function(val) {
console.log(val); // 3
})
Handle multiple Promises
- Promise.all()
- Can pass multiple promises, and call then handler when every promise has been fulfilled
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then(function(values) {
console.log(values);
});
Promise Support
-
Chrome 32, Opera 19, Firefox 29, Safari 8 &
Microsoft Edge - Node 0.12
- Q module
- Util.Promisify added in Node v8
Convert error first Callbacks to Promises
const util = require('util');
const fs = require('fs');
const stat = util.promisify(fs.stat);
stat('.').then((stats) => {
// Do something with `stats`
}).catch((error) => {
// Handle the error.
});
// Or
const stat = util.promisify(fs.stat);
async function callStat() {
const stats = await stat('.');
console.log(`This directory is owned by ${stats.uid}`);
}
Demo 4
Async Await Keywords
- 'async/await' added Node and JavaScript in Node v8
- To make any function asynchronous, place 'async' keyword to the beginning of function
- Place 'await' keyword in front of asynchronous calls like promises instead of using '.then()' handler
- Allows JavaScript developers to write their code in a more synchronous way
- Also available in C# and coming to Swift
function resolveAfter2Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve('resolved');
}, 2000);
});
}
async function asyncCall() {
console.log('calling');
var result = await resolveAfter2Seconds();
console.log(result);
// expected output: 'resolved'
}
asyncCall();
Async/Await behind the scenes
- Async tells the compiler to expect awaits on function
- Await proceeds Promises, but allow synchronous style syntax
- Compiler will divide functions into starting and callback functions
- If you have one 'await' keyword function will be divided into two functions
Demo 5
Async Framework
- Works in Node or Browser
- Parallel workflow
- Waterfall workflow
- forEach workflow
- race workflow
async.map(['file1','file2','file3'], fs.stat, function(err, results) {
// results is now an array of stats for each file
});
async.filter(['file1','file2','file3'], function(filePath, callback) {
fs.access(filePath, function(err) {
callback(null, !err)
});
}, function(err, results) {
// results now equals an array of the existing files
});
async.parallel([
function(callback) { ... },
function(callback) { ... }
], function(err, results) {
// optional callback
});
async.series([
function(callback) { ... },
function(callback) { ... }
]);
Demo 6
Resources
- https://github.com/jaxnode-UG/async
- https://slides.com/davidfekke/async
- https://medium.com/@ajmeyghani/async-javascript-a-pocket-reference-2bb16ac40d21
Questions?
Contact Me
- David Fekke at gmail dot com
- Twitter @jaxnode and @davidfekke
- Skype: davidfekke
- http://jacksonville-tech.com/ Slack Group
Async
By David Fekke
Async
JavaScript is single threaded, yet non-blocking. The reason for this is the asynchonous
- 1,144