Invoking async.series inside async.series produces unpredictable output - node.js

Using caolan's async library for node.js, I've been trying to call a function that uses async.series inside another function that uses async.series, but I still can't get the functions to run in the correct order, as detailed below:
The terminal output shows the second function being called before the first, for no apparent reason:
The "sys" module is now called "util". It should have a similar interface.
Starting the second step in the series
Value of b: undefined
Invoking the function firstStep
the value of toObtain is: [object Object]
And here's the corresponding source code:
var im = require('imagemagick');
var async = require('async');
var toObtain;
var b;
async.series([
function (callback) {
//It appears that this function is being invoked after the second function.
//Why is this happening?
firstStep();
callback();
},
function (callback) {
//Why is the output of this function being displayed BEFORE the output of the function above? It's the opposite of the order in which I'm calling the functions.
console.log("Starting the second step in the series");
console.log("Value of b: " + b);
}]);
function firstStep(){
async.series([
function (next) { // step one - call the function that sets toObtain
im.identify('kittens.png', function (err, features) {
if (err) throw err;
console.log("Invoking the function firstStep");
toObtain = features;
//console.log(toObtain);
b = toObtain.height;
next(); // invoke the callback provided by async
});
},
function (next) { // step two - display it
console.log('the value of toObtain is: %s',toObtain.toString());
}]);
}

After about an hour of experimentation, I got it to work properly. I simply modified the firstStep function so that it takes a callback function as a parameter, and calls the callback at the end of the firstStep function.
var im = require('imagemagick');
var async = require('async');
var toObtain = false;
var b;
async.series([
function (callback) {
firstStep(callback); //the firstStep function takes a callback parameter and calls the callback when it finishes running. Now everything seems to be working as intended.
},
function (callback) {
console.log("Starting the second step in the series");
console.log("Value of b: " + b);
}]);
function firstStep(theCallback){
async.series([
function (next) { // step one - call the function that sets toObtain
im.identify('kittens.png', function (err, features) {
if (err) throw err;
console.log("Invoking the function firstStep");
toObtain = features;
//console.log(toObtain);
b = toObtain.height;
next(); // invoke the callback provided by async
});
},
function (next) { // step two - display it
console.log('the value of toObtain is: %s',toObtain.toString());
theCallback();
}]);
}

Related

Understanding callback on this MDN example

The code comes from an MDN tutorial on how to use Node.js and mongoose. The idea is to make parallel request to get the count of documents in different models. I don't understand where the callback passed to each async.parallel comes from, where it is defined and what it does, it seems like a dummy function to me. Could you help me understand it? Here is the code:
var Book = require('../models/book');
var Author = require('../models/author');
var Genre = require('../models/genre');
var BookInstance = require('../models/bookinstance');
var async = require('async');
exports.index = function(req, res) {
async.parallel({
book_count: function(callback) {
Book.countDocuments({}, callback); // Pass an empty object as match condition to find all documents of this collection
},
book_instance_count: function(callback) {
BookInstance.countDocuments({}, callback);
},
book_instance_available_count: function(callback) {
BookInstance.countDocuments({status:'Available'}, callback);
},
author_count: function(callback) {
Author.countDocuments({}, callback);
},
genre_count: function(callback) {
Genre.countDocuments({}, callback);
}
}, function(err, results) {
res.render('index', { title: 'Local Library Home', error: err, data: results });
});
};
the callback is passed by the async package.
Explanation:
As async parallel function takes array or object (in your example) of asynchronous tasks and these async tasks require a callback which will get called when its execution completes or if there is an error. So parallel function provides these callback functions and will call your callback ( provided as a second parameter to parallel function call) when all of them are called or got an error in any of them.
You can check the detailed explanation from here - https://caolan.github.io/async/v3/docs.html#parallel
Update:
Consider parallel as a wrapper function like:
function parallel(tasks, userCallback) {
let tasksDone = [];
function callback(err, data){ // this is the callback function you're asking for
if(err){
userCallback(err); // calling callback in case of error
}else {
tasksDone.push(data);
if(tasks.length === tasksDone.length){
userCallback(null, tasksDone); // calling callback when all the tasks finished
}
}
}
tasks.forEach(task => {
task(callback); // calling each task without waiting for previous one to finish
})
}
Note: This is not a proper implementation of parallel function of async, this is just an example to understand how we can use callback function internally and what is its usecase

Async.series: Variable is undefined outside of function

I'm new to Node/Javascript and trying to grab a url audioLink by loading another url songLink. I would then add them to a new object rapSong. To achieve this I am using async series so that the calls will be happen asynchronously. I have tried other methods such as using async waterfall and just callbacks with no success.
audioLink is undefined unless I am within the getAudioLink function. Also the functions in the async.series do not run in the correct order. Any advice?
//for each song..
songLinkElem.each(function(i, s) {
var songName = StringUtils.removeWhiteSpacesAndNewLines($(s).children(".title_with_artists").text());
var songLink = $(s).attr("href");
var audioLink;
async.series([
function(callback){
function getAudioLink(songLink, callback){
request(songLink, function(err,resp,body){
$ = cheerio.load(body);
var audioElem = $(body).find(".audio_link > a");
var audioLink = audioElem.attr("href");
return callback(audioLink);
});
}
getAudioLink(songLink, function(resp){
audioLink = resp;
console.log("one");
})
console.log("two");
callback();
},
function(callback){
var rapSong = new Song(songName, artistLink, songLink, audioLink);
rapArtist.addSong(rapSong);
console.log("three");
callback();
}],
function(err, result){
console.log("four");
});
Within your first async.series function, you shouldn't call its callback until the callback for getAudioLink is called.
So move the code that follows the getAudioLink call inside its callback:
getAudioLink(songLink, function(resp){
audioLink = resp;
console.log("one");
console.log("two");
callback();
});
That way the async.series won't proceed until after audioLink is set.

async/flow series on node.js

Consider this code:
var async = require('async');
var a = function()
{
console.log("Hello ");
};
var b = function()
{
console.log("World");
};
async.series(
[
a,b
]
);
Output is
Hello
Why is World not part of the output?
The async.series function passes one callback to each of the methods that must be called before the next one is called. If you change your functions a and b to call the function it will work.
function a(done){
console.log('hello');
done(null, null); // err, value
}
function b(done){
console.log('world');
done(null, null); // err, value
}
For each method called in series it is passed a callback method which must be run, which you are ignoring in your example.
The docs say:
tasks - An array or object containing functions to run, each function
is passed a callback(err, result) it must call on completion with an
error err (which can be null) and an optional result value.
The reason why your code is stopping after the first method is that the callback isn't being run, and series assumed an error occurred and stopped running.
To fix this, you have to rewrite each method along these lines:
var b = function(callback)
{
console.log("World");
callback(null, null) // error, errorValue
};

Callback is not working it is printing as undefined

I had a previous question Object [object Object] has no method 'test' Now the issue with object is gone but the call back is not working the console.log prints undefined
this.test = function(callback) {
callback('i am test');
};
var self = this;
module.exports.config = function (settings, callback) {
self.test(function(err,res){
console.log(res);
});
};
Please ignore as i am new to nodejs
this.test() expects a callback that will be passed a string as the first argument. You are giving it a callback function that uses the second argument, not the first argument. Since no second argument is passed to the callback, you get undefined for it.
Change your call from this:
self.test(function(err,res){
console.log(res);
});
to this:
self.test(function(err){
console.log(err);
});
Or, alternatively change the implementation of this.test() to pass a second argument to its callback.
this.test = function(callback) {
// pass two arguments to the callback
callback('i am test', 'this is me');
};
var self = this;
module.exports.config = function (settings, callback) {
self.test(function(err,res){
// now both arguments err and res are available here
console.log(res);
});
};

Promise with q framework and the callback pattern in Node.js?

Even if well documented q framework is quite hard to understand if you are programming with Node.js for a few days. But I like to learn about it!
var Q = require('q');
var fs = require('fs');
// Make the promise manually (returns a value or throws an error)
var read1 = fs.readFile(fname, enc, function (err, data) {
if(err) throw err;
return data;
});
// Convenient helper for node, equivalent to read1?
var read2 = Q.nfbind(fs.readFile);
// Uh?!
var read3 = function (fname, enc) {
var deferred = Q.defer();
fs.readFile(fname, enc, function (error, text) {
if (error) {
deferred.reject(new Error(error));
} else {
deferred.resolve(text);
}
return deferred.promise;
});
};
// Execute
Q.fncall(read1).then(function (data) {}, function (err) {}).done();
Are read1, read2 and read3 equivalent? Can I use Q.nfbind every time the last parameter of a function accept a callback in the style of function (err, value)?
You have a few errors in your examples.
read1
This is not 'make a promise manually', this is just making a normal asynchronous call. In your code, you call readFile immediately, so read1 would be the return value of readFile which is undefined. To get a behavior similar to read2 and read3 you would need to do something like this:
var read1 = function(fname, env, success, error){
fs.readFile(fname, enc, function (err, data) {
// Throwing here would just crash your application.
if(err) error(err);
// Returning from inside 'readFile' does nothing, instead you use a callback.
else success(data);
});
};
read2
// Not equivalent to read1 because of the notes above,
// Equivalent to read3, with the fixes I mention below.
var read2 = Q.nfbind(fs.readFile);
read3
var read3 = function (fname, enc) {
var deferred = Q.defer();
fs.readFile(fname, enc, function (error, text) {
if (error) {
// 'error' is already an error object, you don't need 'new Error()'.
deferred.reject(error);
} else {
deferred.resolve(text);
}
// HERE: Again returning a value from 'readFile' does not make sense.
return deferred.promise;
});
// INSTEAD: Return here, so you can access the promise when you call 'read3'.
return deferred.promise.
};
You can indeed use nfbind on anything that takes a callback as the last parameter.
With my comments, read2 and read3 accomplish the same goal, which is to create a function that will take a filename and encoding, and return a promise object.
For those, you can do this:
read2('file.txt', 'utf8').then(function (data) {}, function (err) {}).done();
read3('file.txt', 'utf8').then(function (data) {}, function (err) {}).done();
For read1, you would call it like this:
read1('file.txt', 'utf8', function (data) {}, function (err) {});
Update
Standard promises have evolved a bit since this was answered, and if you are leaning toward read3, I'd recommend doing the following:
var read4 = function (fname, enc) {
return Q.promise(function(resolve, reject){
fs.readFile(fname, enc, function (error, text) {
if (error) {
// 'error' is already an error object, you don't need 'new Error()'.
reject(error);
} else {
resolve(text);
}
});
});
};
This is more in line with standard ES6 promises, and with bluebird, so you'll have an easier time with the code moving forward. Using the method mentioned in read3 also introduces the possibility of synchronously throwing exceptions instead of capturing them in the promise chain, which is usually undesirable. See the deferred antipattern.

Resources