In the previous article we looked at sequential asynchronous code and how to tame it using functional programming.
We also introduced how to use asynchronous function composition using composite and partial function application using ap to reduce complexity. We will be re-using those techniques so I recommend you read the previous article and familiarize yourself with them
Today we will look at parallel code and how we can use asynchronous iterators to manage their complexity
An example of parallel code would be
fs.readdir("/tmp", function (err, fileNames) {
var count = files.length
, files = []
for (var i = 0; i < files.length; i++) {
fs.readFile(files[i], function (err, file) {
if (err) {
return next(err)
}
files.push(file)
count--
if (count === 0) {
next(null, files)
}
})
}
function next(err, results) {
// results is an array of files
// do something
}
})
The above code reads all the files in the /tmp folder and then calls next with an array of the file content.
What it's actually doing is two actions, one is reading the directory and the second is mapping all the file names to the actual file content.
Although it's not technically mapping the array because fs.readFile
is asynchronous and returns in arbitrary order. We are using files.push(file)
which will just turn files
into an array of the files in random order. This is an easy mistake to make.
This means we can represent this code as a composition of reading the directory and mapping the files to their content.
var action = composeAsync(
mapToFiles
, fs.readdir
)
action("/tmp", doSomething)
function doSomething(err, results) {
// do something
}
function mapToFiles(err, fileNames, callback) {
iterators.map(fileNames, fs.readFile, callback)
}
In this case iterators.map takes a list (either an object or array) and calls an iterator function (in this case fs.readFile) for each value in the list (each fileName in the array).
iterators assumes your iterator function applies to the standard asynchronous syntax of callback last and that the callback is called with (err, data). When we write code that complies with this standard node pattern it simplifies a lot of the complexity.
iterators.map
itself will take the result value from the callback and replace the initial value in the list with it. iterators.map
also keeps a running count of whether all the asynchronous functions are finished; when they are iterators.map
will call the third parameter - your done callback -with the mapped list.
For more details read the implementation of iterators.map
iterators.map
itself will take the result value from the callback and replace the initial value in the list with it.iterators.map
also keeps a running count of whether all the asynchronous functions are finished; when they areiterators.map
will call the third parameter - your done callback - with the mapped list.