how to understand callbacks and async.parallel in node - javascript

I'm very new to JavaScript and callbacks, so my apologies if this is a stupid question. Based on the async docs for parallel, I came up with this example code that executed the expected way based on the docs:
async = require('async')
async.parallel([
function(callback){
setTimeout(function(){
callback(null, 'one');
}, 800);
},
function(callback){
setTimeout(function(){
callback(null, 'two');
}, 100);
}
],
function(err, results){
console.log(results)
})
Running this with node foo.js prints a results array ['one', 'two'] as the docs indicate it will. The thing I don't understand is how exactly this works: when you pass callback as a parameter to each function and then callback is called as callback(null, res), what exactly is being called her? I've not actually defined any function callback, nor have I passed any sort of operating callback function as a parameter, yet everything magically works fine. Am I totally missing the point here? Or is this actually the under-the-hood magic of async?

You are not the one passing callback to the task functions, the async module is. It's a special function that the module passes to your task functions that when called, checks if any more tasks are left.
Here is something similar to what is being done inside async:
function myParallel(tasks, finalcb) {
var tasksLeft = tasks.length,
results = [],
ignore = false;
function callback(err, result) {
if (ignore) return;
if (err) {
ignore = true;
finalcb && finalcb(err);
} else if (--tasksLeft === 0) {
ignore = true;
finalcb && finalcb(null, results);
} else
results.push(result);
}
tasks.forEach(function(taskfn) {
taskfn(callback);
});
}
myParallel([
function(callback) {
setTimeout(function() {
callback(null, 'one');
}, 800);
},
function(callback) {
setTimeout(function() {
callback(null, 'two');
}, 100);
}
], function(err, results){
console.log(results)
});

Related

Don't understand the inner callback in async.parallel()

I need help understanding async.parallel(tasks, callback).
From what I understand, the list of tasks are executed, and once they're finished, all the results are then passed to callback.
An example of it in action is:
async.parallel([
function(callback) {
setTimeout(function() {
console.log('Task One');
callback(null, 1); <----- DON'T UNDERSTAND
}, 200);
},
function(callback) {
setTimeout(function() {
console.log('Task Two');
callback(null, 2); <----- DON'T UNDERSTAND
}, 100);
}
],
function(err, results) {
console.log(results);
// the results array will equal [1, 2] even though
// the second function had a shorter timeout.
});
The part that I don't understand however, are the inner calls to callback. Specifically, what does it mean when we call callback(null, 1) and callback(null, 2)? What do the null and the 1 or 2 refer to?

Executing multiple HTTP requests sequentially using async.js

How can I execute multiple HTTP requests sequentially using async.js . I checked the async.js documentation but couldn't figure out, how to do that. I want to achieve the same thing as below code using async.js callback style.
var http = require('http');
var Q = require('q');
var URL="http://localhost:3000";
var getPromise=function(url) {
var deferred = Q.defer();
var req = http.get(url, function(response) {
if(response.statusCode < 200 || response.statusCode > 299){
deferred.reject(new Error('ErrorCode '+response.statusCode))
}
var result="";
response.on('data',function(chunk){result +=chunk;} )
response.on('end',function(){deferred.resolve(result);} )
});
req.on('error',function(err){
console.error('Error with the request:', err.message);
deferred.reject(err);
});
req.end();
return deferred.promise;
}
getPromise('http://localhost:3000/olympic/2016/ranking/4')
.then(function(data){
console.log("Response 1 "+data)
return getPromise(URL+'/iso/country/'+JSON.parse(data).Country);
})
.then(function(data){
console.log("Response 2 "+data)
return getPromise(URL+'/olympic/2016/medal/'+JSON.parse(data).iso);
})
.then(function(data){
console.log("Response 3 "+data)
})
.catch(function(err){
console.log(err)
});
I got it, I needed async.waterfall it takes an array of functions and execute them one by one. Also we can pass result from previous function execution to the next one
var async = require('async');
async.waterfall([
function task1(done) {
console.log('start!');
setTimeout(function(){
console.log("T1 Complete");
// <- set value to passed to step 2
done(null, 'Value from step 1');
},5000);
},
function task2(task1Result, done) {
console.log(task1Result);
setTimeout(function(){
console.log("T2 Complete");
// <- set value to passed to step 3
done(null, 'Value from step 2');
},1000);
},
function task3 (task2Result, done) {
console.log(task2Result);
setTimeout(function(){
console.log("T3 Complete");
// <- no value set for the next step.
done(null);
},100);
}
],
function (err) {
if (err) {
throw new Error(err);
} else {
console.log('No error happened in any steps, operation done!');
}
});
Looking over the code a bit and trying to understand it more, I believe that async.waterfall is the function you'll need. What this will do is run each function in order, passing its results to the next function in the sequence. Here's an example:
async.waterfall([
function(callback)
{
// Function 1: do request here...
callback(null, val); // replace null with a value if you want the waterfall to error and go straight to the end
},
function(val, callback) {
// Function 2: do your second request here
callback(null, val1, val2, val3); // you can pass any number of variables you like, just make sure the next function in the sequence expects them
},
function(val1, val2, val3, callback)
{
// Function 3: do your third request here
callback(null, result);
} // this can go on for as long as you like
], function(err, result)
{
// this will be called immediately if the first parameter in any of the callbacks is not null, or when all the functions have run
});

async each callback is called before iteration

i have the following routing function:
router.route('/api/teamUsersWithStat/:team_id')
.get(function (req, res) {
var user_stat = academy_team_user_stat.build();
user_stat.usersByTeam(req.params.team_id, function (result) {
if (result) {
async.each(result, function () {
var i = 0;
user_stat.findModulesTaken(res.user_id, res.team_id, function (modules) {
result[i].modules = modules;
i++;
});
}, res.json(result))
} else {
res.status(401).send("Team not found");
}
}, function (error) {
res.send("Team not found");
});
});
as you can see im using the async.each method to collect additional data to my existing array.
However the res.json(result) is called without it running the actual loop.
(i can tell this as in my javascript i am debugging the response).
So what am i doing wrong?
You're calling your res.json method straight away, you're also reinitializing i inside the loop so it's always 0.
Also, each requires a callback in order to procede to the next iteration.
The following is how I'd do it:
async.each(result, function (r, callback) {
user_stat.findModulesTaken(res.user_id, res.team_id, function (modules) {
result[result.indexOf(r)].modules = modules;
callback();
});
}, function(err) {
if(err)
return res.json(err);
res.json(result);
});
res.json(result) is called as a function, and therefore invoked immediately. To make sure res.json is invoked after the async.each(), you need to pass a function as callback:
async.each(result, function () {
...
}, function(err) {
if(!err) res.json(result);
));

node.js async.series is that how it is supposed to work?

var async = require('async');
function callbackhandler(err, results) {
console.log('It came back with this ' + results);
}
function takes5Seconds(callback) {
console.log('Starting 5 second task');
setTimeout( function() {
console.log('Just finshed 5 seconds');
callback(null, 'five');
}, 5000);
}
function takes2Seconds(callback) {
console.log('Starting 2 second task');
setTimeout( function() {
console.log('Just finshed 2 seconds');
callback(null, 'two');
}, 2000);
}
async.series([takes2Seconds(callbackhandler),
takes5Seconds(callbackhandler)], function(err, results){
console.log('Result of the whole run is ' + results);
})
The output looks like below :
Starting 2 second task
Starting 5 second task
Just finshed 2 seconds
It came back with this two
Just finshed 5 seconds
It came back with this five
I was expecting the takes2Second function to finish completely before the takes5Second starts.
Is that how it is supposed to work. Please let me know.
And the final function never runs. Thanks.
Not quite. You are executing the functions immediately (as soon as the array is evaluated), which is why they appear to start at the same time.
The callback passed to each of the functions to be executed is internal to the async library. You execute it once your function has completed, passing an error and/or a value. You don't need to define that function yourself.
The final function never runs in your case because the callback function that async needs you to invoke to move on to the next function in the series never actually gets executed (only your callbackHandler function gets executed).
Try this instead:
async.series([
takes2Seconds,
takes5seconds
], function (err, results) {
// Here, results is an array of the value from each function
console.log(results); // outputs: ['two', 'five']
});
James gave you a good overview of async.series. Note that you can setup anonymous functions in the series array and then call your actual functions with parameters
var async = require('async')
var param1 = 'foobar'
function withParams(param1, callback) {
console.log('withParams function called')
console.log(param1)
callback()
}
function withoutParams(callback) {
console.log('withoutParams function called')
callback()
}
async.series([
function(callback) {
withParams(param1, callback)
},
withoutParams
], function(err) {
console.log('all functions complete')
})
My preferred way to create the async series is using operational array as follow;
var async = require('async'),
operations = [];
operations.push(takes2Seconds);
operations.push(takes5seconds);
async.series(operations, function (err, results) {
// results[1]
// results[2]
});
function takes2Seconds(callback) {
callback(null, results);
}
function takes5seconds(callback) {
callback(null, results);
}
async.series
([
function (callback)
{
response=wsCall.post(user,url,method,response);
console.log("one");
callback();
}
,
function (callback)
{
console.log("two");
//console.log(response);
callback();
}
]
,
function(err)
{
console.log('Both a and b are saved now');
console.log(response);
});
In async.series,all the functions are executed in series and the consolidated outputs of each function is passed to the final callback. e.g
var async = require('async');
async.series([
function (callback) {
console.log('First Execute..');
callback(null, 'userPersonalData');
},
function (callback) {
console.log('Second Execute.. ');
callback(null, 'userDependentData');
}
],
function (err, result) {
console.log(result);
});
Output:
First Execute..
Second Execute..
['userPersonalData','userDependentData'] //result

Waterfall method for async.js hanging when calling mongoose save method

I'm trying to use the async waterfall method but when it gets to one of the functions, it hangs. I suspect it's because the save() operation is too slow for the execution context, but that's why I was starting to use async's waterfall, so I can wait for the value returned until it goes to the next function in the series (passing along the proper data with it which would be the counted in my case below).
// In my user controller:
async.waterfall([
function(callback) {
getSubmission(id, function(submission) {
if (submission) {
callback(null, submission);
}
});
},
function(submission, callback) {
var submissionId = submission._id;
getViews(submissionId, ip, function(count) {
if (count) {
callback(null, count, submissionId);
}
});
},
// Those top two functions work perfectly passing what
// I need to this one which is where I'm having trouble
function(views, submissionId, callback) {
// addView is called because it is actually
// inserting a row in the db, but never returns from the caller
addView(submissionId, ip, function(added) {
// this callback doesn't fire
if (added) {
callback(null, added);
}
});
},
function(added, callback) {
console.log(added);
}
]);
This is what addView() is (also within user controller which is where the previous async.waterfall code also is in) :
var addView = function(submissionId, ip, callback) {
Submission.addView({
submissionId : submissionId,
ip: ip
}, function(err, counted) {
if (err) {
throw err;
}
if (counted) {
callback(counted);
}
});
};
This is what it's calling (inside my Submission model file) when it calls Submission.addView():
exports.addView = function(obj, fn) {
var ip = obj.ip,
submissionId = obj.submissionId,
submissionView = new SubmissionView(obj);
// it gets to this point
submissionView.save({
ip : ip,
submission_id : submissionId
}, function(err, counted) {
fn(err, counted);
});
};
Whenever async "hangs", it's usually because a callback hasn't been called.
You need to make sure that you call the callback in all code paths. I would also recommend that you reserve the first parameter of any async callback to be an error, even if you don't use it as that is the pattern used throughout node.js. Some modules rely on this pattern. e.g. domains.
If you make the below change, then I would expect some error to pop up somewhere:
getSubmission(id, function(submission) {
if (submission) {
callback(null, submission);
}
});
should be something like this:
getSubmission(id, function(err, submission) {
if(err){
return callback(err);
}
if (!submission) {
return callback('no submission found');
}
callback(null, submission);
});

Categories

Resources