I am working in node js express framework and I have a scenario where I have to call 2-3 nested callback functions inside for loop.
Below is my code:
for (i in jdp_tb_trades) {
var jdp_response_json_parsed = JSON.parse(jdp_tb_trades[i].jdp_response);
processConsign(jdp_tb_trades[i].tid, function(err_process_consign, lpnumber) {
if (err_process_consign) {
console.log("Some error occurred in processConsign. Error is:" + err_process_consign);
//Check if generate XML is enabled from admin end.
configuration.getOneByKey('generateXml', function(err_configuration, result_configuration) {
if (err_configuration) {
console.log('[generateXml]: generate xml enabled/disabled - No response.');
return callback(null, lpnumber);
} else {
if (result_configuration.value == 'true') {
console.log('[generateXml]: generate xml enabled.')
generateXml(jdp_tb_trades[i].tid, jdp_response_json_parsed, lpnumber, function(err_generate_xml, success_generate_xml);
if (err_generate_xml) {
return callback(err_generate_xml);
} else {
return callback(null, success_generate_xml);
}
});
} else {
console.log('[generateXml]: generate xml disabled.');
return callback(null, lpnumber);
}
}
});
} else {
//Check if generate XML is enabled.
configuration.getOneByKey(
'generateXml',
function(err_configuration, result_configuration) {
if (err_configuration) {
console.log('[generateXml]: generate xml enabled/disabled - No response.');
return callback(null, lpnumber);
} else {
if (result_configuration.value == 'true') {
console.log('[generateXml]: generate xml enabled.')
generateXml(jdp_tb_trades[i].tid, jdp_response_json_parsed, lpnumber, function(err_generate_xml, success_generate_xml) {
if (err_generate_xml) {
return callback(err_generate_xml);
} else {
return callback(null, success_generate_xml);
}
});
} else {
console.log('[generateXml]: generate xml disabled.');
return callback(null, lpnumber);
}
}
});
});
}
Update
The above code is part of a function named getOrders which is called as:
module.exports = {
getOrders: function (callback) {
getOrders(function(err_getOrders, getOrdersResponse){
if(err_getOrders){
console.log("generate Order process error:"+err_getOrders);
return callback(err_getOrders);
}
else{
console.log("generate Order process success:"+getOrdersResponse);
return callback(null, getOrdersResponse);
}
});
},
}
I have made multiple callbacks because function ends in multiple scenarios. I am not concerned about output of getOrders because I am not going to consume that output anywhere.
Here I have two functions processConsign and generateXml. generateXml is called in callback of processConsign. But I think forloop does not wait for these two tasks to get complete and keep increment the loop without waiting for processing of these two functions.
Is there any way by which I can make for loop wait for completion of these two processes and then executing the next loop?
you can use the async.each
async.each(jdp_tb_trades, (jdp_tb_trade, callback) => {
// do manipulation here
// return callback() after the process. pass err if error
}, loop_ended (err) => {
if (err) {
// Error in loop | err callback returned with err
}
// loop already ended here
});
Kindly check this
const async = require('async');
function getOrders (callback) {
async.each(jdp_tb_trades, generate_xml, (err) => {
if (err) {
// the callback return err using callback(err)
}
else {
// check the jdp_tb_trades. no error found
}
});
}
function generate_xml (jdp_tb_trade, callback) {
let jdp_response_json_parsed;
try {
jdp_response_json_parsed = JSON.parse(jdp_tb_trade.jdp_response);
} catch (err) {
return callback(err);
}
processConsign(jdp_tb_trade.tid, (err_process_consign, lpnumber) => {
if (err_process_consign) {
console.log(`Some error occurred in processConsign. Error is: ${err_process_consign}`);
//Check if generate XML is enabled from admin end.
configuration.getOneByKey('generateXml', (err_configuration, result_configuration) => {
if (err_configuration) {
console.log('[generateXml]: generate xml enabled/disabled - No response.');
// base on your callback it still a success response
// return callback(null, lpnumber);
jdp_tb_trade.lpnumber = lpnumber;
return callback();
}
else {
if (result_configuration.value == 'true') {
console.log('[generateXml]: generate xml enabled.')
generateXml(jdp_tb_trade.tid, jdp_response_json_parsed, lpnumber, (err_generate_xml, success_generate_xml) => {
if (err_generate_xml) {
jdp_tb_trade.err_generate_xml = err_generate_xml;
// return error
return callback(err_generate_xml);
} else {
jdp_tb_trade.success_generate_xml = success_generate_xml;
return callback();
// return callback(null, success_generate_xml);
}
});
}
else {
console.log('[generateXml]: generate xml disabled.');
return callback(null, lpnumber);
}
}
});
}
else {
if (result_configuration.value == 'true') {
console.log('[generateXml]: generate xml enabled.')
generateXml(jdp_tb_trades[i].tid, jdp_response_json_parsed, lpnumber, (err_generate_xml, success_generate_xml) => {
if (err_generate_xml) {
return callback(err_generate_xml);
} else {
jdp_tb_trade.success_generate_xml = success_generate_xml;
// return callback(null, success_generate_xml);
return callback();
}
});
}
else {
console.log('[generateXml]: generate xml disabled.');
jdp_tb_trade.lpnumber = lpnumber;
return callback();
// return callback(null, lpnumber);
}
}
});
}
Related
I'm trying to do something if the value is not exist, so I can update it. but isExist function always return undefined. what can I do with this?
reference: Ero is already defined.
async.forEachOf(idArray, function(value, key, cb) {
rp(baseURL + value)
.then(function(json) {
if (!isExist(json)) {
// do something
} else {
console.log(isExist(json), "It's not video or exists");
}
})
.catch(function(err) {
console.error(err);
})
cb();
}, function() {
res.status(200)
});
});
function isExist(data) {
let parsedData = JSON.parse(data);
if (parsedData.items[0].type === 'Video') {
Ero.find({
videoUri: parsedData.items[0].url_mp4
}, function(err, docs) {
if (docs.length) {
return true;
} else {
return false;
}
})
} else {
return false;
}
}
Let's look at your isExist function.
function isExist(data) {
let parsedData = JSON.parse(data);
if (parsedData.items[0].type === 'Video') {
Ero.find({
videoUri: parsedData.items[0].url_mp4
}, function(err, docs) {
if (docs.length) {
return true;
} else {
return false;
}
})
} else {
return false;
}
}
In that function you have two branches at the conditional. When the condition is false the else block will run – this returns false. When the condition is true the first block will run however there is no return statement, therefore implicitly returning undefined.
You say "why does it not have a return statement?", I'm pretty sure I have one.
It looks like you have one here.
if (docs.length) {
return true;
} else {
return false;
}
However look at which function it is returning out of. It is only returning out of the callback function passed to Ero.find, it does not return out of isExist.
You ask "what can I do about this?".
I am assuming Ero.find is an asynchronous function, therefore isExist will become an asynchronous function too. To do async functions in JavaScript you can use Promises or async functions.
Here's some example code of what isExist might look like with a Promise.
function isExist(data) {
/**
* `isExist` returns a Promise. This means the function _promises_ to have a value resolved in the future.
*/
return new Promise((resolve, reject) => {
let parsedData = JSON.parse(data);
if (parsedData.items[0].type === 'Video') {
Ero.find({
videoUri: parsedData.items[0].url_mp4
}, function(err, docs) {
if (docs.length) {
/**
* Once `Ero.find` has completed, `resolve` `isExist` with a value of `true`, otherwise `resolve` `isExist` with a value of `false`.
*/
resolve(true);
} else {
resolve(false);
}
})
} else {
/**
* You can resolve a Promise even without performing an asynchronous operation.
*/
resolve(false);
}
});
}
Further reading
Async & Performance, a book in the series You Don't Know JS by Kyle Simpson.
Promises in JavaScript
Async functions in JavaScript
Using JSON.parse will put you in a risk to have an exception if the JSON has a syntax error. Use a try / catch block.
Without knowing your data I cannot say what else is wrong on your check.
function isExists(data){
try{
var parsedData = JSON.parse(data);
if (parsedData.items[0].type === 'Video') {
Ero.find({
videoUri: parsedData.items[0].url_mp4
}, function(err, docs) {
if (docs.length) {
return true;
} else {
return false;
}
})
} else {
return false;
}
}catch(e) {
// any error
return false;
}
}
I try to use async NodeJs library to execute a render with express after collecting informations of a API but the async.parallel callback execute itself before collecting all data that I need
This is the code :
LolApi.getMatchHistory(app_dtb.summoner.id, "euw", function(err, history) {
if (!err) {
async.parallel([
function(callback) {
LolApi.getMatch(history.matches[nbMatch].matchId, function(err, match) {
if (!err) {
var teamIn;
function getParticipantNb() {
for (var i = 0; i < match.participantIdentities.length; i++) {
if (app_dtb.summoner.id == match.participantIdentities[i].player.summonerId) {
if (i <= 5) teamIn = 100
else teamIn = 200
return i + 1
}
}
return false;
}
var participantNb = getParticipantNb()
if (match.teams[0].winner == true && teamIn == 100) {
app_dtb.lastGame.won = "Win";
} else {
app_dtb.lastGame.won = "Loose";
}
console.log(app_dtb.lastGame.won)
} else {
console.log(err)
}
});
setTimeout(function() {
callback(null, "one");
}, 200);
},
function(callback) {
options = {
champData: 'allytips,blurb',
version: '4.4.3',
locale: 'en_US'
}
LolApi.Static.getChampionById(history.matches[nbMatch].champion, options, function(err, champ) {
if (!err) {
console.log(champ.name);
app_dtb.lastGame.champName = champ.name
} else {
console.log(err)
}
});
setTimeout(function() {
callback(null, "two");
}, 100);
}
], function(err, results) {
console.log(results)
res.render("index");
});
} else {
console.log(err);
}
})
Any idea or other way to have the same result ?
Thanks a lot
You should call the callback inside your LolApi methods callback and be sour that the async callback will be called for two parallel functions eventually. so may the timeout called before your LolApi callbacks.
Response.json should execute after foreach loop completes its execution
var todoarr = (req.body.data) ? req.body.data : undefined
todoarr.forEach(function(element) {
if(element.done == true) {
TodoService.removeTodo(element, function(success) {
});
}
});
res.json("success");
You can try to use async.js http://caolan.github.io/async/ .
each method http://caolan.github.io/async/docs.html#each
Or you can try use Promise.all.
For example:
let promiseArr = [];
todoarr.forEach(function(element) {
if(element.done == true) {
promiseArr.push(somePromiseMethod(element));
}
});
//now execute promise all
Promise.all(promiseArr)
.then((result) => res.send("success"))
.catch((err) => res.send(err));
More info here. https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
Some promise example:
function somePromiseMethod(element) {
return new Promise((resolve,reject) => {
TodoService.removeTodo(element, function(success) {
resolve();
});
});
}
Hope this helps.
You can't send multiple responses on single request, the only thing you can do it's a single response with the array of results:
es with async:
const async = require('async')
// FIX ME: this isn't correctly handled!
const todoarr = (req.body.data) ? req.body.data : undefined
let results = []
async.each(todoarr, function(element, callback) {
console.log('Processing todo ' + element)
if(element.done == true) {
TodoService.removeTodo(element, function(err, success) {
if(err){
callback(err)
} else {
results.push(success)
callback(null, success)
}
})
}
}, function(err) {
if(err) {
console.log('A element failed to process', err)
res.status(500).json(err)
} else {
console.log('All elements have been processed successfully')
// array with the results of each removeTodo job
res.status(200).json(results)
}
})
You can send the response inside the callback function of forEach.
Modify your function so that it will call res.json() on the last iteration only.
Example:
var todoarr = (req.body.data) ? req.body.data : undefined
todoarr.forEach(function(element,index) {
if(element.done == true) {
TodoService.removeTodo(element, function(success) {
});
}
if(index==todoarr.length-1){
res.json("success");
}
});
However, it may not be according to coding standards but it can definitely solve the problem.
var data = [10,21,33,40,50,69];
var i = 0;
var dataSeq = [];
while(i<data.length){
if(data[i]%2 == 0){
store.findOne({'visibility': true},function(err, data){
dataSeq.push(i)
i++;
});
}
else{
dataSeq.push(i)
i++;
}
}
if(i==data.length-1){
console.log(dataSeq) // Should Print [1,2,3,4,5]
return res.status(200).send({ message: 'Task Completed'})
}
I want to collect data as per loop excecutes.
I am aware about how to handle async calls in nodejs. But I want the callbacks in sequence.
e.g. Though there is a async call in if condition i want to hault the loop, so that I can push value of i in dataSeq and it will result in [1,2,3,4,5] array. I want that sequence because my post operations are dependent on that sequence.
I think asyncjs#eachSeries has what you need.
Your code would become something like this:
async.each(data, (item, callback) => {
if(item%2 == 0){
store.findOne({'visibility': true},function(err, data){
dataSeq.push(i)
i++;
});
}
else{
dataSeq.push(i)
i++;
}
}, (err) => {
// if any of the callbacks produced an error, err would equal that error
});
You can use something like async#eachOf
var async = require('async');
var data = [10,21,33,40,50,69];
var dataSeq = [];
async.eachOf(data, function(value, key, cb) {
if (value % 2 == 0) {
store.findOne({ 'visibility': true })
.then(function(doc) {
dataSeq.push(key);
})
.catch(function(err) {
return cb(err);
});
} else {
cb();
}
}, function(err) {
if (err) {
console.error(err)
return res.status(500).send(); # handle the error as you want
}
return res.status(200).send({ message: 'Task Completed'})
})
In short.
I need to access two or more remote resource feeds, combine it and show it as one result from my nodejs service.
In detail
I need to fetch the feeds from multiple providers (which may vary in number according to what is stored in dashboard object)
Concatenate them, do some other data manipulations and show the content as one array at the end.
var allFeeds = [];
dashboard.providers.forEach(function(provider) {
if (provider.source === 'facebook') {
...
fb.getFeeds(provider.data.id, function(feeds) {
...
Array.prototype.push.apply(allFeeds, feeds);
});
} else if (provider.source === 'google') {
...
google.getFeeds(provider.data.id, function(feeds) {
...
Array.prototype.push.apply(allFeeds, feeds);
});
} else if (provider.source === 'twitter') {
...
twitter.getFeeds(provider.data.id, function(feeds) {
...
Array.prototype.push.apply(allFeeds, feeds);
});
}
});
...
// other data manipulations
...
res.json(allFeeds);
As nodejs is having asynchronous network calls how can I achieve this?
You can use async.
var async = require('async');
var allFeeds = [];
var tasks = [];
dashboard.providers.forEach(function (provider) {
if (provider.source === 'facebook') {
...
tasks.push(function (done) {
fb.getFeeds(provider.data.id, function (feeds) {
...
Array.prototype.push.apply(allFeeds, feeds);
done();
});
});
} else if (provider.source === 'google') {
...
tasks.push(function (done) {
google.getFeeds(provider.data.id, function (feeds) {
...
Array.prototype.push.apply(allFeeds, feeds);
done();
});
});
} else if (provider.source === 'twitter') {
...
tasks.push(function (done) {
twitter.getFeeds(provider.data.id, function (feeds) {
...
Array.prototype.push.apply(allFeeds, feeds);
done();
});
});
}
});
async.parallel(tasks, function () {
...
// other data manupulations
...
res.json(allFeeds);
});
You can also check out this post I wrote to structure your code to better manage async operations
you can achieve that with promises I will show you with bluebird.js
var Promise = require('bluebird');
var fbFeedAsync = Promise.promisify(fb.getFeeds);
var googleFeedAsync = Promise.promisify(google.getFeeds);
var twitterFeedAsync = Promise.promisify(twitter.getFeeds);
function getFeedFor(type, id) {
if (type === 'twitter') {
return twitterFeedAsync(id);
} else if (type === 'google') {
return googleFeedAsync(id);
} else if (type === 'facebook') {
return fbFeedAsync(id);
}
}
var feedRequests = dashboard.providers.map(function(provider) {
return getFeedFor(provider.source, provider.data.id);
});
Promise.all(feedRequests).then(function(allFeeds) { // you can use Promise.settle (depending on your use case)
console.log(allFeeds);
});