I have asynchronous query in Node.js. Variable sq3 is a connection variable.
For example something like this:
for (var i in res) {
if (i == 1) {
sq3.query("SELECT * from students;",
function (err, res) {
if (err) {
throw err;
} else {
if (res.length == 1) {
//do something
} else {
//break for
}
}
});
sq3.end();
}
}
How can I break from callback function?
Thanks
Just do it like this, using recursion instead of loops. Not only does this allow you to achieve the logic you want. It also doesn't spin up a bunch of async requests at once. They execute in turn, but asynchronously, so it's still performant.
function lookatEntireResponse(res) {
function lookAtItemInResponse(item) {
if(item == 1) {
sq3.query("SELECT * from students;",
function(err, res) {
if (err)
{
throw err;
}
else
{
if(res.length==1)
{
doSomething(item);
lookAtItemInResponse(res.shift());
}
else
{
//just don't call the next lookAtItemInResponse function, effectively same thing as "break;"
}
}
});
sq3.end();
} else {
lookAtItemInResponse(res.shift());
}
}
lookAtItemInResponse(res.shift());
}
You can consider throttling simultaneous requests with similar logic (say allowing 10 such requests per lookAtItem call. This way you can achieve a hybrid of the two methods, and then just optimize the number of simultaneous requests for performance. The Async library makes stuff like this easier.
In your code fragment, you can't break from the within the callback function. In node.js, callback functions run at an unspecified later time on the same thread. This means but the time you callback function executes, the for loop has long since finished.
To get the effect you want, you need to restructure you code quite significantly. Here's an example of how you could do it (untested!!). The idea is to keep calling doSomething() with the list of items, shrinking it by one element each time until the desired result is achieved (your break condition).
function doSomething(res)
{
while (res.length > 0)
{
i = res.shift(); // remove the first element from the array and return it
if (i == 1)
{
sq3.query("SELECT * from students;",
function(err, res) {
if (err)
{
throw err;
}
if (res.length==1)
{
//do something
// continue with the remaining elements of the list
// the list will shrink by one each time as we shift off the first element
doSomething(res);
}
else
{
// no need to break, just don't schedule any more queries and nothing else will be run
}
});
sq3.end();
break; // break from the loop BEFORE the query executes. We have scheduled a callback to run when the query completes.
}
}
}
for (var i = 0; i < res.length; i++) {
if (i == 1) {
sq3.query("SELECT * from students;",
function (err, res) {
if (err) {
throw err;
} else {
if (res.length == 1) {
//do something
} else {
i = res.length
}
}
});
sq3.end();
}
}
Related
I'm learning node and would like to optimize the code I did. I tried using Async.parallel to perform the operations and when finished return a json.
I am new to js node and I'm trying to do with async.parallel but I returned [Function] in other code that I'm trying to understand it
getTabletIntRout: function(req, res) {
var reqMAC = req.param('id_tablet');
Tablet.findOne(reqMAC).populate('rout_tablet').exec(function(err, tablet) {
if (err) return next(err);
if (!tablet) return next();
var arrRoutes = tablet.rout_tablet;
if (arrRoutes.length > 0) {
var routesNotRemoved = [];
arrRoutes.forEach(function(route) {
if (route.removed == 'no') {
Rout.findOne(route.id)
.populate('rout_assigned') // Pin
.populate('in_rout') // Tablet
.populate('rout_description_assigned')
.exec(function(err, rout2) {
var arrRout = rout2.rout_assigned;
var routsNotRemoved = [];
if (arrRout.length > 0) {
arrRout.forEach(function(ruta) {
if (ruta.removed == 'no') {
routsNotRemoved.push(ruta);
}
});
}
var arrTablets = rout2.in_rout;
var tabletsNotRemoved = [];
if (arrTablets.length > 0) {
arrTablets.forEach(function(tab) {
if (tab.removed == 'no') {
tabletsNotRemoved.push(tab);
}
});
}
var arrDesc = rout2.rout_description_assigned;
var descripNotRemoved = [];
if (arrDesc.length > 0) {
arrDesc.forEach(function(desc) {
if (desc.removed == 'no') {
descripNotRemoved.push(desc);
}
});
}
rout2.rout_assigned = routsNotRemoved;
rout2.in_rout = tabletsNotRemoved;
rout2.rout_description_assigned = descripNotRemoved;
routesNotRemoved.push(rout2);
});
}
});
setTimeout(function() {
if (routesNotRemoved.length > 0) {
res.json({ info: routesNotRemoved });
} else {
return res.json({"error": "-1", "message": "Todas las rutas asociadas a esa tablet están eliminadas"});
}
}, 2000);
} else {
return res.json({"error": "-2", "message": "No existen rutas asociadas en esa tablet"});
}
}););});}},
I will try to provide some thoughts, hopefully some will make sense in your domain.
Extract a function to make people understand what you're doing in that big function
So instead of
Tablet.findOne(reqMAC).populate('rout_tablet').exec(function(err, tablet) { // ...
You would have
Tablet.findOne(reqMAC).populate('rout_tablet').exec(meaningfulFunctionName);
Don't repeat yourself
So your code becomes shorter and whenever the reader of your code finds a function name he / she already knows what is happening inside that
if (arrRout.length > 0) {
arrRout.forEach(function(ruta) {
if (ruta.removed == 'no') {
routsNotRemoved.push(ruta);
}
});
}
No need to check for empty arrRout as the argument function to arrRout.forEach will simply not run in the case of length being zero.
What you wrote is just a filter function, so why not using filter? Like so
arrRout.filter(function(ruta) {
return ruta.removed == 'no';
});
You can also reuse this, if you extract the anonymous function, for arrTablets and arrDesc.
On the argument: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
Don't use a huge if else
Either check for the inverse or return a default or something that makes sense in your domain, but don't have that big chunk of logic, it makes it hard to reason about your code.
Extract more function so that it's easier to use async
You might want to have something like this
async.waterfall([
function(next) {
// here you can put
// Tablet.findOne(reqMAC).populate('rout_tablet').exec
// invoke next with err, tablet
},
function(tablet, next) {
async.each(arrRoutes, function(arrRoute, nextEach) {
// write your code logic here
});
}
], function() {
// decide what to invoke res.json with
});
Remember to extract functions after you're done putting the logic inside the async steps, I didn't do it so it is more clear where to put what.
I hope this makes sense, feel free to ask if you have any doubts.
Next time you post a question please make sure to properly indent it, don't just paste it here.
I'm having problem calling async function inside a while loop.
the problem is 'while' statement will end before its underlying function result appear and thats because it's async function.
the code is like below:
while (end < min) {
db.collection('products').count({
tags: {
$in: ['tech']
}
}, function(err, result) {
if (result) {
a = result;
}
});
max = min;
min = max - step;
myitems.push(a);
}
res.send(myitems);
and at the end i could not send the result because all of while iteration should finish before sending the final result.
how could i modify the code to solve such a problem?
thanks in advance
Without using third party libraries, here's a method of manually sequencing your async operations. Note, because this is async, you have to process the results inside of the next() function when you see that you are done iterating.
// assume that end, max, min and step are all defined and initialized before this
var results = [];
function next() {
if (end < min) {
// something seems missing from the code here because
// this db.collection() call is always the same
db.collection('products').count({tags: {$in: ['tech']}}, function(err, result) {
if (!err && result) {
results.push(result);
max = min;
min - max - step;
next();
} else {
// got an error or a missing result here, provide error response
console.log("db.collection() error or missing result");
}
}
} else {
// all operations are done now
// process the results array
res.send(results);
}
}
// launch the first iteration
next();
You could leverage a 3rd party library to do this as well (non working performQuery example using async):
function performQuery(range, callback) {
// the caller could pre calculate
// the range of products to retrieve
db.collection('products').count({
tags: {
$in: ['tech'],
// could have some sort of range query
$gte: range.min,
$lt: range.max
}
}, function(err, result) {
if (result) {
callback(result)
}
});
}
async.parallel([
performQuery.bind(null, {min: 0, max: 10}),
performQuery.bind(null, {min: 10, max: 20})
], function(err, results) {
res.send(results);
});
I'm updating an object in an array with Mongoose. After it updates I'm firing res.send() in the callback.
If I don't fire the res.send() the update saves fine. But when I do res.send(), the entire object in the array is erased from Mongo.
landmarkSchema.findById(tileRes.worldID, function(err, lm) {
if (!lm){
console.log(err);
}
else if (req.user._id == lm.permissions.ownerID){
var min = tileRes.zooms[0];
var max = tileRes.zooms.slice(-1)[0];
if (lm.style.maps.localMapArray){
for (var i = 0; i < lm.style.maps.localMapArray.length; i++) { //better way to do this with mongo $set
if (lm.style.maps.localMapArray[i].map_marker_viewID == req.body.map_marker_viewID) {
lm.style.maps.localMapArray[i]['temp_upload_path'] = '';
lm.style.maps.localMapArray[i]['localMapID'] = tileRes.mapURL;
lm.style.maps.localMapArray[i]['localMapName'] = tileRes.worldID;
lm.markModified('style.maps.localMapArray');
lm.save(function(err, landmark) {
if (err){
console.log('error');
}
else {
console.log('updated map',landmark);
//res.status(200).send(landmark);
}
});
}
}
}
}
});
Is it a write issue where Mongo doesn't finish writing before res.send is fired?
I recommend start using async to iterate in these cases, of course you can do this without it, but you'll have to find a very good reason to avoid using it.
I don't see the rest of your method, but using async it should be similar to this:
async.each(lm.style.maps.localMapArray, function(localMap, callback) {
// Do whatever you like here
//....
// Call method that requires a callback and pass the loop callback
localMap.iUseACallbackAfterDoingMyStuff(callback);
}, function(err){
// now here the loop has ended
if( err ) {
// Something happened..
} else {
res.status(200).send(somethingToTheClient);
}
});
The code snippet is just for you to get the idea.
Maybe the results ain't weird, but I started using Node 1-2 months ago so for me they are...
I have a loop which sorts out every other value of the array returned by hgetall (Redis command) and in that loop I call a function to get all values from another table with keys stored in the sorted array. This was more difficult to explain than I thought. Here's my code:
Pastebin: http://pastebin.com/tAVhSUV1 (or see below)
function getInfo (cn, callback) {
var anArray = [];
redis_client.hgetall('chat_info:' + cn, function (err, vals) {
if(err) { throw err; }
for(i in vals) {
anArray.push(vals[i]);
}
return callback(anArray);
});
}
redis_client.hgetall('chat_rooms:' + POST.chat_name, function (err, val) {
if(err) { throw err; }
var vars = [],
rArr = [];
for (i in val) {
vars.push(i);
}
for(var i = 0; i < vars.length; i += 1) {
if(i%2 === 0) {
getInfo(vars[i], function (hej) {
rArr.push(hej);
});
}
}
});
The callback from the call to getInfo() is executed after the entire loop. Am I missing out on something here? Because it can't do that, right? (when I use rArr (right after the loop) it's empty, nbBut if I log it in the callback it gets logged after everything else written after the loop)
Yes, that's probably normal.
Understand that callbacks are executed after the hgetall call. Which mean that when the redis functions receive somehting it will call the callbacks. In other words, all the callbacks can be executed later.
As javascript only works in one thread, the calls to hgetall should be blocking to be executed as they come in the for loop. But as you're more certainly using async IO. The for loop ends and then it will start calling each callbacks that were queued inside the javascript event loop.
Edit
Unfortunately, to achieve what you're trying to do, you should wrap your code inside many other callbacks. You can use this project to make it easier: https://github.com/caolan/async
You should be able to install it using npm install async.
You'd have to do something like that:
function getInfo (cn) {
return function(callback) {
var anArray = [];
redis_client.hgetall('chat_info:' + cn, function (err, vals) {
if(err) { throw err; }
for(i in vals) {
anArray.push(vals[i]);
}
return callback(anArray);
});
};
}
redis_client.hgetall('chat_rooms:' + POST.chat_name, function (err, val) {
if(err) { throw err; }
var vars = [],
rArr = [],
callbacks = [];
for (i in val) {
vars.push(i);
}
for(var i = 0; i < vars.length; i += 1) {
if(i%2 === 0) {
callbacks.push(getInfo(vars[i]));
}
}
async.series(callbacks, function (err, results) {
// Final code here
});
});
Basically, I want to pit two asynchronous calls against each other, and only use the winner.
I cannot seem to figure out how to do this, only how to prevent it. Is it remotely possible?
Lame pseudo-code:
//rigging a race
function MysqlUser()
{
setTimeout(function(){
return "mysqluser";
}, 500);
}
function ADUser()
{
setTimeout(function(){
return "aduser";
}, 1000);
}
function getUser()
{
var user = null;
user = ADBind();
user = MysqlBind();
//if user != null
return user;
//else?
}
I'd like (in this instance) for MysqlUser to win out over ADUser.
Any help would be appreciated.
You can write a simple first function that takes a list of task and calls back with the result of only the first to complete:
function first(tasks, cb) {
var done = false;
tasks.forEach(function(task) {
task(function(result) {
if(done) return;
done = true;
cb(result);
});
});
}
Then:
function mysqlUser(cb) {
setTimeout(function() {
cb("mysqluser");
}, 500);
}
function adUser(cb) {
setTimeout(function() {
cb("aduser");
}, 1000);
}
first([mysqlUser, adUser], function(user) {
console.log(user);
});
This might need some more thought if you want to cope with both operations failing.