Async series's callback doesn't fire - javascript

My problem here is that when I use this code I always get the callback in ensureAppIsValid and the one in the Async series seems to be never fired
var ReviewProcess = function (args) {
'use strict';
assert(args.application, 'Need an application to review');
this.app = args.application;
};
ReviewProcess.prototype.ensureAppIsValid = function (callback) {
'use strict';
if (this.app.isValid()) {
callback(null, this.app);
} else {
callback(this.app.validationMessage(), null);
}
};
ReviewProcess.prototype.processApplication = function (callback) {
'use strict';
async.series([
this.ensureAppIsValid(callback)
], function (err, callback) {
if (err) {
return callback(null, {
success: false,
message: err
});
}
callback(null, {
success: true,
message: 'Welcome to Mars'
});
});
};

It looks like you're using the word 'callback' too many times and the code isn't doing what you're expecting it to. You are passing the top level callback into the ensureAppIsValid() function, so once that function executes it doesn't go to async's callback. It also looks like you don't need the extra callback in async's follow up.
How about this:
ReviewProcess.prototype.processApplication = function (callback) {
'use strict';
async.series([
this.ensureAppIsValid(cb)
], function (err) {
if (err) {
return callback(null, {
success: false,
message: err
});
}
callback(null, {
success: true,
message: 'Welcome to Mars'
});
});
};

Async series requires a lists of functions as tasks to be ran.
You pass
this.ensureAppIsValid(callback)
But this is the call of the function, not the function itself.
Try this:
Async.series([
this.ensureAppIsValid.bind.apply(this.ensureAppIsValid, [null, [callback]])
], ... )

You shouldn't pass argument callback to this.ensureAppIsValid(). Instead, use here local callback parameter. For example, named cb.
Try:
var ReviewProcess = function (args) {
'use strict';
assert(args.application, 'Need an application to review');
this.app = args.application;
};
ReviewProcess.prototype.ensureAppIsValid = function (callback) {
'use strict';
if (this.app.isValid()) {
callback(null, this.app);
} else {
callback(this.app.validationMessage(), null);
}
};
ReviewProcess.prototype.processApplication = function (callback) {
'use strict';
async.series([
this.ensureAppIsValid(cb)
], function (err, callback) {
callback(null, {
success: !err,
message: err? err : 'Welcome to Mars'
});
}
});
};
I have also slightly changed your eventual callback in async.series. Now it's more compact.

Related

Node.js - Execute functions one by one

I am trying to run these functions one by one, after it will execute first function move to second one and so on .. now it will render both functions at the same time, it is taking so much memory when there are over 3000 functions.
webshot('google.com', 'google.jpeg', options, function(err) {});
webshot('yahoo.com', 'yahoo.jpeg', options, function(err) {});
That last argument to each of those is called a "callback;" it gets called when the work has been finished. So if you want to do these one at a time, put the call to the next one inside the callback of the previous one:
webshot('google.com', 'google.jpeg', options, function(err) {
webshot('yahoo.com', 'yahoo.jpeg', options, function(err) {});
});
Of course, if you have a bunch of these (you mentioned 3000!), you're not just going to nest them like that. I would probably create an array of the arguments you want to pass them, and then use a callback loop:
function process(list, callback) {
var index = 0;
doOne();
function doOne() {
var entry = list[index++];
webshot(entry.domain, entry.img, entry.options, function(err) {
// ...error handling, etc...
if (index < list.length) {
doOne();
} else {
callback();
}
});
}
}
var webshots = [
{domain: 'google.com', img: 'google.jpeg', options: options},
{domain: 'yahoo.com', img: 'yahoo.jpeg', options: options},
// ...
];
process(webshots, function() {
// All done
});
Side note: This would be a bit cleaner with Promises. There are various libraries that will promise-ify Node-style callback APIs (like webshot's), you might look at doing that.
If you did, you could handle those promises like this:
var webshots = [
{domain: 'google.com', img: 'google.jpeg', options: options},
{domain: 'yahoo.com', img: 'yahoo.jpeg', options: options},
// ...
];
allDone = webshots.reduce(function(p, entry) {
return p.then(function() {
return promisifiedWebshot(entry.domain, entry.img, entry.options);
});
}, Promise.resolve());
allDone.then(function() {
// All done
})
.catch(function() {
// Handle error
});
You can use a control flow library like async:
'use strict';
const async = require('async');
async.series([
(callback) => {
webshot('google.com', 'google.jpeg', options, callback);
},
(callback) => {
webshot('yahoo.com', 'yahoo.jpeg', options, callback);
}
], (error) => {
if(error) {
console.log('Error: ' + error);
}
});
There are also utilities function like map, each, eachOf to allow you to directly iterate throught your list of url and apply to the call:
'use strict';
const async = require('async'),
urls = [
{url: 'google.com', img: 'google.jpeg'},
{url: 'yahoo.com', img: 'yahoo.jpeg'}
];
async.map(urls, (url, callback) => {
webshot(url.url, url.img, options, callback);
//I presumed webshot call the callback like callback(error, result)
}, (error, webshots) => {
//If a error occured, async will skip there and error will be set
//Else, webshots will be a array of all the results
});

async.js return value form last function

I would like create find method in my User object. This function should returns user. But for this example it returns only the text.
But I don't know how return value for waterfall.
When I run
console.log(User.find("575578a9f95d6de1354327ef"));
I got 'undefined' in my output, but I except 'function find shoud return this value', What I should to do if I want get 'function find shoud return this value' text on my output
User = {
collectionName: 'users',
find: function(id){
async.waterfall(
[
function(callback) {
MongoClient.connect('mongodb://127.0.0.1:27017/lingogo', function(err,db) {
if(err) { throw err}
callback(null, db, id);
});
},
function(db,id, callback) {
var collection = db.collection(User.collectionName);
collection.find({'_id': ObjectID(id)}).toArray(function (err, result) {
if (err) { throw err };
if (result[0] && result[0]._id != '') {
return callback(null,result[0]);
}
return callback(null,null);
})
},
],
function (err, user) {
return 'function find shoud return this value';
}
);
}
}
console.log(User.find("575578a9f95d6de1354327ef"));
Function find must have a callback too, that you call in a callback of waterfall. You cannot return a value synchronously from an asynchronous function.
find: function (id, callback) {
async.waterfall(..., function (...) {
callback(null, return_value);
});
}
That should be called like
User.find("575578a9f95d6de1354327ef", function (err, return_value) {
console.log(return_value);
});

can I use async.waterfall inside async.parallel?

I want to call two functions and get the results in parallel but one of the function's results needed to be adapted. So the function structure is:
function test(cb) {
async.parallel({
func1: function foo(cb1) {
cb(null, {});
},
func2: function bar(cb2) {
async.waterfall([
function adapt1(next) {
//do something;
},
function adapt2(next) {
//do something;
}
], function handler(err, res) {
//do something.
})
}
}, function handler2(err, res) {
cb(null, {});
})
}
However, it just seems hang there forever. not sure if I can use async in this way....
Sure you can! You have to be sure to call your callbacks in the correct order and in the first place. For example, func1 should be calling cb1 not cb. Secondly, your waterfall is not invoking their callbacks at all.
Take this code for example.
'use strict';
let async = require('async');
function test(callback) {
async.parallel({
func1: function(cb) {
cb(null, { foo: 'bar' });
},
func2: function(cb) {
async.waterfall([
function(cb2) {
cb2(null, 'a');
},
function(prev, cb2) {
cb2(null, 'b');
}
], function(err, result) {
cb(err, result);
});
}
}, function(err, results) {
callback(err, results);
});
}
test(function(err, result) {
console.log('callback:', err, result);
});
Outputs: callback: null { func1: { foo: 'bar' }, func2: 'b' }

nodejs async each function

In node js, i am using async function to get the result from another function and store it as array and return the result. But here i am getting empty json as output {}. See the comments inside code block. May i know where i am doing mistake ?
collectSearchResult: function (searchConfig, callback) {
var searchResult = {};
async.each(searchConfig.scope, function (scope, callback) {
var query = {
"query": {
"match": {
"_all": searchConfig.q
}
},
operationPath = scope.url;
this.doSearch(operationPath, query, function (err, results) {
var type = scope.type;
searchResult[type] = results;
// Here i am able to get correct output async
console.log(searchResult);
});
callback();
}.bind(this), function (err) {
// Here it is just returning empty json like {}. this function is called before this.doSearch complete its task
console.log(searchResult);
callback(err, searchResult);
});
}
collectSearchResult: function (searchConfig, callback) {
var searchResult = {};
async.each(searchConfig.scope, function (scope, callback) {
var query = {
"query": {
"match": {
"_all": searchConfig.q
}
},
operationPath = scope.url;
this.doSearch(operationPath, query, function (err, results) {
var type = scope.type;
searchResult[type] = results;
// Here i am able to get correct output async
console.log(searchResult);
//<><><><><><><>
callback(); //you need to place the callback for asynch.each
//within the callback chain of your query, else async.each
//immediately finishes before your data has arrived.
//<><><><><><><>
});
}.bind(this), function (err) {
// Here it is just returning empty json like {}. this function is called before this.doSearch complete its task
console.log(searchResult);
callback(err, searchResult);
});
}

Retry an async function if the error is retryable

How can I change my logic to retry if the err.retryable = true in the following code:
async.each(queues, function (queue, callback) {
sqs.getQueueUrl({'QueueName': queue.queue}, function (err, qurl) {
if (err) {
if (err.retryable) {
// How to retry sqs.getQueueUrl({'QueueName': queue.queue}...?
} else {
console.error(err, err.stack);
callback(err);
}
}else{
//Do lots of things here
}
})
}, function (err) {
//...
})
In addition to the advice by dfsq to name your callback and use it an asynchronously recursive manner, see also async.retry from the async module by Caolan McMahon. Example:
async.retry(3, apiMethod, function(err, result) {
// do something with the result
});
More complex example:
async.auto(
{
users: api.getUsers.bind(api),
payments: async.retry(3, api.getPayments.bind(api))
}, function(err, results) {
// do something with the results
}
);
More details in the docs.
UPDATE
A better solution for your use case:
I wrote a utility function that you can use to make your original method support any number of retries (with err.retryable support).
You can use it this way:
var retryingFunction = withRetries(sqs, sqs.getQueueUrl);
(Note that you need to provide both sqs and sqs.getQueueUrl)
And now you can use the retryingFunction just like you would use sqs.getQueueUrl but with a number of retries as the first arguments. The retries will only be done when err.retryable is true.
So now, instead of:
sqs.getQueueUrl({'QueueName': queue.queue}, function (err, qurl) {
// ...
});
you can use:
retryingFunction(3, {'QueueName': queue.queue}, function (err, qurl) {
// ...
});
where 3 is the number of retries.
And this is the function that I wrote to make the above possible:
function withRetries(obj, method) {
if (!method) {
method = obj;
obj = null;
}
if (typeof method != "function") {
throw "Bad arguments to function withRetries";
}
var retFunc = function() {
var args = Array.prototype.slice.call(arguments);
var retries = args.shift();
var callback = args.pop();
if (typeof retries != "number" || typeof callback != "function") {
throw "Bad arguments to function returned by withRetries";
}
var retryCallback = function (err, result) {
if (err && err.retryable && retries > 0) {
retries--;
method.apply(obj, args);
} else {
callback(err, result);
}
};
args.push(retryCallback);
method.apply(obj, args);
};
return retFunc;
}
See this LIVE DEMO to play with it and see how it works.
It works fine in the demo, I hope it will also work for your code.
You can give queue callback a name and provide it in retry request again. Try this:
async.each(queues, function (queue, callback) {
sqs.getQueueUrl({'QueueName': queue.queue}, function queueCallback(err, qurl) {
if (err) {
if (err.retryable) {
sqs.getQueueUrl({'QueueName': queue.queue}, queueCallback);
} else {
console.error(err, err.stack);
callback(err);
}
} else {
//Do lots of things here
}
});
}, function (err) {
//...
});

Categories