Find inside callback of another find (...), how to escape from callback hell? - javascript

(First: I'm sorry, I don't speak english very well!)
I wanna return the results of 3 finds in one array.
My code (next) is running well, but I'm in callback hell!
_Schema
.static('retrieveAll', function(cb) {
query = {};
this.find(query, function(err, data) {
if(err) {
cb(err, null);
return;
}
if(data)
all = data;
else
all = [];
_StoresModel.find(query).select('contact address').exec(function(err, data) {
if(err) {
cb(err, null);
return;
}
if(data) {
all = data.reduce(function(coll, item) {
coll.push(item);
return coll;
}, all);
}
_CustomersModel.find(query).select('contact address').exec(function(err, data) {
if(err) {
cb(err, null);
return;
}
if(data) {
all = data.reduce(function(coll, item) {
coll.push(item);
return coll;
}, all);
}
cb(null, all);
});
});
});
});
I've a FIND inside a FIND inside a FIND.
Is there anyway to improve this?
SOLUTION:
_Schema
.static('retrieveAll', function(cb) {
var model = this;
_async.parallel(
{ contacts: function(cb) {
model.find({}).exec(cb);
}
, stores: function(cb) {
_StoresModel.find({}).select('contact address').exec(cb);
}
, costumers: function(cb) {
_CostumersModel.find({}).select('contact address').exec(cb);
}
}
, function(err, data) {
if(err) {
cb(err, null);
return
}
var ret = [];
if(data.contacts.length > 0) {
ret = ret.concat(data.contacts);
}
if(data.stores.length > 0) {
ret = ret.concat(data.stores);
}
if(data.costumers.length > 0) {
ret = ret.concat(data.costumers);
}
cb(null, ret);
});

You can try using Promises.
(untested) example:
var RSVP = require('rsvp');
var all = [];
_Schema.static('retrieveAll', function(cb) {
query = {};
findPromise(this, query)
.then(function (data) {
all = data;
return findPromise(_StoresModel, query, 'contact address');
})
.then(function (stores) {
all = all.concat(stores);
return findPromise(_CustomersModel, query, 'contact address');
})
.then(function (customers) {
all = all.concat(customers);
cb(null, all);
})
.catch(function (err) {
cb(err, null);
});
});
function findPromise(Model, query, select) {
return new RSVP.Promise(function (resolve, reject) {
Model.find(query).select(select || '*').exec(function (err, data) {
return err ? reject(err) : resolve(data);
});
});
}
That example is using RSVP, but there are also other promise implementations such as Q and bluebird.
And a side note, you can use concat to concatenate arrays instead of using reduce.

take a look at npm Async. It a great library of different patterns that can be used on node.js.
You will probably want to use the waterfall if there is a Chronological priority or parallel pattern if they can all execute in parallel.

Some server-side promise libraries like q and bluebird would clean up your code substantially and eliminate the mess of callback hell.

Related

Node js Query Async Map function is call

function(dataValue, cb) {
req.app.db.models.User.find({
_id: { $ne: dataValue._id }
}, function(err, totalUser) {
if (!err) {
var len = totalUser.length;
if (len !== 0) {
req.app.utility.async.map(totalUser, function(each, callback) {
console.log(each);
req.app.utility.async.mapSeries(each.nonregisterContact, function(element, callback1) {
console.log('element', element.number);
console.log('dataValue', dataValue.mobileNumber);
console.log('kolka', Number(element.number) === Number(dataValue.mobileNumber));
if (Number(element.number) === Number(dataValue.mobileNumber)) {
each.registerContact.push(dataValue._id.toString());
each.nonregisterContact.splice(element, 1);
each.save(function(err, finalResult) {
if (!err) {
} else {
console.log(err);
}
})
callback1(null, null);
} else {
callback1(null, null);
}
}, function(err, final) {
if (!err) {
callback(null, null);
} else {
console.log(err);
}
});
}, function(err, result) {
if (!err) {
console.log('2');
return cb(null, dataValue);
} else {
console.log(err);
}
});
} else {
return cb(null, dataValue);
}
} else {
cb(err);
}
})
}
I don't get any response after each.save method call in the mapSeries method final callback.I am trying this solution.How i will do the same thing. How I resolve that and handle this kind of situation?
I tried to simplify code, but I'm not sure that my code realizes your needs. Also I cann't test it :D
dataValue, each, element, finalResult are very common names, so you should use them with caution to keep code is readable/supportable.
// very bad idea is include other libraries to app
var async = require('async');
var db = require('db'); // this module must export connection to db
...
function (dataValue, cb) {
// processUser use data from closure of current function => inside of current
function processUser (user, callback) {
async.mapSeries(user.nonregisterContact, function(contact, callback){
// Check and exit if condition is not satisfied. It's more readable.
if (Number(contact.number) !== Number(dataValue.mobileNumber))
return callback(null); // ignore user
user.registerContact.push(dataValue._id.toString());
user.nonregisterContact.splice(contact, 1);
user.save(function(err, finalResult) { // Is finalResult ignore?
if (err)
console.log(err);
callback(); // ingnore error
})
}, callback);
db.models.User.find({_id: { $ne: dataValue._id }}, function(err, userList) {
if (!err)
return cb(err);
if (userList.length == 0)
return cb(new Error('Users not found'));
// use named function to avoid stairs of {}
async.map(userList, processUser, cb);
})
};

Make multiple callbacks from node js asynchronous function

How can I return a object of data returned by asynchronous function called multiple times from within a asynchronous function.
I'm trying to implement like this :
var figlet = require('figlet');
function art(dataToArt, callback)
{
var arry[];
figlet(dataToArt, function(err, data) {
if (err) {
console.log('Something went wrong...');
console.dir(err);
return callback('');
}
arry[0] = data;
callback(arry);
});
figlet(dataToArt, function(err, data) {
if (err) {
console.log('Something went wrong...');
console.dir(err);
return callback('');
}
arry[1] = data;
callback(arry);
});
}
art('Hello World', function (data){
console.log(data);
});
How can I do it correctly, I searched and searched but couldn't find a solution.
Ps. I'm using Figlet.js
I don't know if you're ok using an external module, but you can use tiptoe.
Install it using npm install tiptoe like any regular module and it basically goes like this:
var tiptoe = require('tiptoe')
function someAsyncFunction(obj, callback) {
// something something
callback(null, processedData);
}
tiptoe(
function() {
var self = this;
var arr = ['there', 'are', 'some', 'items', 'here'];
arr.forEach(function(item) {
someAsyncFunction(item, self.parallel());
});
},
function() {
var data = Array.prototype.slice.call(arguments);
doSomethingWithData(data, this);
},
function(err) {
if (err) throw (err);
console.log('all done.');
}
);
the someAsyncFunction() is the async function you want to call does something and calls the callback parameter as a function with the parameters error and data. The data parameter will get passed as an array item to the following function on the tiptoe flow.
Did it Myself :) Thanks to mostafa-samir's post
var figlet = require('figlet');
function WaterfallOver(list, iterator, callback) {
var nextItemIndex = 1;
function report() {
nextItemIndex++;
if(nextItemIndex === list.length)
callback();
else
iterator([list[0],list[nextItemIndex]], report);
}
iterator([list[0],list[1]], report);
}
var FinalResult = [];
WaterfallOver(["hello","Standard","Ghost"], function(path, report) {
figlet.text(path[0], { font: path[1] }, function(err, data) {
if (err) {
FinalResult.push("Font name error try help");
report();
return;
}
data = '<pre>.\n' + data + '</pre>';
FinalResult.push(data);
report();
});
}, function() {
console.log(FinalResult[0]);
console.log(FinalResult[1]);
});

keystone.js nested promise -> foreach -> list find scope issue

I am writing an service, where I retrieve a list of items from a another service, then iterate over result performing keystone.list operation(s).
I am loosing the return status in the find/exec operation. I have tried promises, async, etc.
If someone could point out the correct way to implement this, I would appreciate it.
general implementation:
exports = module.exports = function (req, res) {
var rtn = {
added: 0,
count: 0
}
service(params)
.then(function(svcResult) {
svcResult.forEach(function(item) {
rtn.count++; // <-- correctly seen in apiresponse
Artifact.model.find()
.where({ artifactId: item.id})
.exec(function(err, result) {
if (result.length == 0) {
result = new Artifact.model({
... populate from item ....
});
result.save();
rtn.added++; // <-- not seen in api response
});
});
res.apiResponse(rtn);
});
}
for starters, exec is an async call, which you are ignoring in your res.apiResponse and thus count is incremented and not added, to make life easy, I am moving the exec call outside and wrapping it with promise:
function pExec(id){
return new Promise(function(resolve, reject){
Artifact.model.find()
.where({ artifactId: id})
.exec(function(err, result){
console.log('result: ', result); // there is a possibility that this is not empty array, which seems to be the only case when you increment added value
err? reject(err): resolve(result);
});
});
}
exports = module.exports = function(req, res){ // I think it is 'exports' not 'exposts'
service(params)
.then(function(svcResult) {
var promises = svcResult.map(function(item){
rtn.count++;
return pExec(item.id).then(function(result){
if (result.length == 0) {
result = new Artifact.model({
//... populate from item ....
});
result.save(); // again this might be an async call whose response you might need before incrementing added...
rtn.added++; // <-- not seen in api response
};
});
});
Promise.all(promises).then(function(){
res.apiResponse(rtn);
});
});
}
Thanks... Here is what I have come up with so far....
function getArtifact(id) {
return new Promise(function (resolve, reject) {
Artifact.model.findOne()
.where({artifactId: id})
.exec(function (err, artifact) {
err ? resolve(null) : resolve(artifact);
});
});
}
function createArtifact(item) {
return new Promise(function (resolve, reject) {
var artifact = new Artifact.model({
// ... populate from item ....
});
artifact.save(function (err, artifact) {
err ? resolve(null) : resolve(artifact);
});
});
}
exports = module.exports = function (req, res) {
var rtn = {
success: false,
count: 0,
section: '',
globalLibrary: {
Added: 0,
Matched: 0
},
messages: [],
};
if (!req.user || !req.user._id) {
rtn.messages.push("Requires Authentication");
return res.apiResponse(rtn);
}
if (!req.params.section) {
rtn.messages.push("Invalid parameters");
return res.apiResponse(rtn);
}
var userId = req.user._id;
var section = req.params.section;
rtn.section = section;
service(section)
.then(function (svcResult) {
if (svcResult.length == 0 || svcResult.items.length == 0) {
rtn.messages.push("Retrieved empty collection");
return;
}
rtn.messages.push("Retrieved collection");
var artifacts = svcResult.items(function (item) {
rtn.count++;
return getArtifact(item.objectid)
.then(function (artifact) {
if (!artifact || artifact.length == 0) {
rtn.messages.push("Global Library Adding: " + item.name['$t']);
rtn.globalLibrary.Added++;
artifact = createArtifact(item);
} else {
rtn.globalLibrary.Matched++;
}
return artifact;
})
});
Promise.all(artifacts)
.then(function () {
rtn.success = true;
res.apiResponse(rtn);
});
});
}

Callback is not a function in mongoose.find({})

I am new to Node.js and mongoose, i am trying to query objects from a mongo collection using find({}) and the function is as follows :
schema.statics.listAllQuizes = function listAllQuizes(){
Model.find({},function(err,quizes,cb){
if(err){
return cb(err);
}else if(!quizes){
return cb();
}
else {
return cb(err,quizes);
}
});};
But when i call this function i get an error saying
return cb(err,quizes);
^
TypeError: cb is not a function
I am stuck at this point, can someone please help me with this, thanks in advance.
The callback should an argument to listAllQuizes, not an argument to the anonymous handler function.
In other words:
schema.statics.listAllQuizes = function listAllQuizes(cb) {
Model.find({}, function(err, quizes) {
if (err) {
return cb(err);
} else if (! quizes) {
return cb();
} else {
return cb(err, quizes);
}
});
};
Which, logically, is almost the same as this:
schema.statics.listAllQuizes = function listAllQuizes(cb) {
Model.find({}, cb);
};
Here's an example on how to use it:
var quiz = App.model('quiz');
function home(req, res) {
quiz.listAllQuizes(function(err, quizes) {
if (err) return res.sendStatus(500);
for (var i = 0; i < quizes.length; i++) {
console.log(quizes[i].quizName)
}
res.render('quiz', { quizList : quizes });
});
}
Assuming you have code somewhere that looks like this:
foo.listAllQuizzes(function (err, quizzes) {
...
});
Then your function listAllQuizzes is passed a callback:
schema.statics.listAllQuizzes = function (cb) {
Model.find({}, function(err, quizzes) {
if (err) return cb(err);
cb(null, quizzes);
});
};

How can I use node async to fetch my mongoose calls?

I'm building a site with node/express/mongoose and it needs to do the following things when viewing a submission.
The problem I'm running into is doing db fetches in a non-serial fashion. For example, I'll do a few calls to fetch some data, but some of the calls might not finish until the execution context goes to the other. Tried to use the npm module, async, but am having trouble trying to figure out how I would integrate it.
Here is my code:
var getViewCount = function(submissionId) {
Submission.getSubmissionViewCount({
submissionId : submissionId
}, function(err, count) {
if (err) {
throw err;
}
if (count) {
return count;
}
});
};
var getVotes = function(submissionId) {
console.log('getvotes');
Submission.getSubmissionVotes({
submissionId : submissionId
}, function(err, votes) {
return votes;
});
};
var getSubmission = function(id) {
Submission.getSubmission({
id : id
}, function(err, submission) {
if (err) {
throw err;
}
if (submission) {
return submission;
}
});
};
var renderSubmission = function(title, submission, views) {
res.render('submission', {
title: submission.title + ' -',
submission: submission,
views: views.length
});
};
How do I use this with async? Or should I be using async.series isntead of async.async?
async.series([
function(callback) {
var submission = getSubmission(id);
callback(null, submission);
},
function(callback) {
// getViewCount(submissionId);
},
function(callback) {
// getVotes(submissionId);
},
function(callback) {
//renderSubmission(title, submission, views);
}
], function(err, results) {
console.log(results);
});
Basically I want to fetch the views and votes first and then render my submission.
TheBrain's description of the overall structural changes that you should make to your code is accurate. The basic methodology in Node is to nest a series of callbacks; very rarely should you require functions that actually return a value. Instead, you define a function that takes a callback as parameter and pass the result into that callback. Please review the code below for clarification (where cb is a callback function):
var getViewCount = function(submissionId, cb) {
Submission.getSubmissionViewCount({
submissionId : submissionId
}, function(err, count) {
if (err) {
throw err;
}
if (cb) {
cb(count);
}
});
};
var getVotes = function(submissionId, cb) {
console.log('getvotes');
Submission.getSubmissionVotes({
submissionId : submissionId
}, function(err, votes) {
if (cb) {
cb(votes);
}
});
};
var getSubmission = function(id, cb) {
Submission.getSubmission({
id : id
}, function(err, submission) {
if (err) {
throw err;
}
if (cb) {
cb(submission);
}
});
};
var renderSubmission = function(submissionId) {
getSubmission(submissionId, function (submission) {
if (!submission) {
// unable to find submission
// add proper error handling here
} else {
getViewCount(submissionId, function (viewCount) {
res.render('submission', {
title: submission.title + ' -',
submission: submission,
views: viewCount
});
});
}
};
};

Categories