Asyncjs/seriesByHand.js
fs.readdir('.', function(err, filenames) {
if (err) throw err;
function readFileAt(i) {
var filename = filenames[i];
fs.stat(filename, function(err, stats) {
if (err) throw err;
if (! stats.isFile()) return readFileAt(i 1);
fs.readFile(filename, 'utf8', function(err, text) {
if (err) throw err;
concatenation = text;
if (i 1 === filenames.length ) {
’ to ’ never ’ to ’ never to ’ });
}
readFileAt(0);
});
As you can see, the asynchronous version has a lot more code than the synchronous version. If you use synchronous methods such as filter and forEach, the number of lines of code is only about half, and it is much easier to read. How nice it would be if there were asynchronous versions of these nifty iterators! Use Async.js to do just that!
You may have noticed that in the above code example, the author ignored the advice I gave in Section 1.4: throwing exceptions from callbacks is a bad design, especially in a production environment. However, there is no problem with a simple example that throws an exception directly. If an unexpected error occurs in your code, throw will shut down the code and provide a nice stack trace explaining the cause of the error.
What’s really wrong here is that the same error handling logic (i.e. if(err) throw err) is repeated as many as 3 times! In Section 4.2.2, we'll see how Async.js can help reduce this duplication.
Functional writing method of Async.js
We want to replace the filter and forEach methods used by synchronous iterators with corresponding asynchronous methods. Async.js gives us two options.
async.filter and async.forEach, which process the given array in parallel.
async.filterSeries and async.forEachSeries, they will process the given array sequentially.Running these asynchronous operations in parallel should be faster, so why use a sequential approach? There are two reasons.
There is an upper limit on the number of files that Node and any other application process can read simultaneously. If this upper limit is exceeded, the operating system will report an error. If you can read the file sequentially, you don't need to worry about this limitation.
So now let’s understand async.forEachSeries first. The data collection method of Async.js is used below, and the code implementation of the synchronous version is directly rewritten.
function isFilename(filename, callback) {
fs.stat(filename, function(err, stats) {
if (err) throw err;
callback(stats.isFile());
}
function readAndConcat(filename, callback) {
fs.readFile(filename, 'utf8', function(err, fileContents) {
if (err) return callback(err);
concatenation = fileContents ;
});
}
function onComplete(err) {
if (err) throw err;
console.log(concatenation);
}
Now our code is beautifully divided into two parts: task overview (in the form of async.filter call and async.forEachSeries call) and implementation details (in the form of two iterator functions and a completion callback onComplete).
filter and forEach are not the only Async.js utility functions that correspond to standard functional iteration methods. Async.js also provides the following methods:
reject/rejectSeries, just the opposite of filter;
map/mapSeries, 1:1 transformation;
reduce/reduceRight, the gradual transformation of values;
detect/detectSeries, find the value matching the filter;
sortBy, produces an ordered copy;
some, tests whether at least one value meets the given criteria;
every, tests whether all values meet the given criteria.
These methods are the essence of Async.js, allowing you to perform common iterative work with minimal code duplication. Before continuing to explore more advanced methods, let's look at error handling techniques for these methods.
Async.js error handling technology
If you want to blame it, blame Node’s fs.exists for being the first to do this! And this also means that iterators that use Async.js data collection methods (filter/filterSeries, reject/rejectSeries, detect/detectSeries, some, every, etc.) cannot report errors.
For all Async.js iterators that are not boolean, passing a non-null/undefined value as the first parameter of the iterator callback will immediately call the completion callback with the error value. This is why readAndConcat works without throw.
Asyncjs/forEachSeries.js
Asyncjs/forEachSeries.js