How to handle results of multiple async callbacks in Node - javascript

I'm working on a scenario in Node which involves multiple async.map calls, one of which is nested in an _.map function as I need access to nested arrays. I'm struggling to properly combine the results of these calls into an array which I return in the end.
More specifics on the scenario:
I start with a userId and query my games db to find all games which this userId is associated with. I pull gameId and description for each game.
I then query the gameId of each of these games to find all userIds associated with each game. I filter the original userId out of these results.
Next I query my users db for each of these userIds in order to get details such as user email and name. (Note: this is where _.map comes into play as the userIds are nested in an array of arrays; each inner array representing a game).
The last step, and where I'm struggling, is how to combine these results and return them. My final desired array looks like the below. An array of game objects. Each game object has a gameId, description, and users property. users is an array of users associated with that game and has id, email, and name properties.
[
{
gameId: 'dfh48643hdgf',
description: 'lorem ipsum...',
users: [
{
id: 1,
email: 'test#example.com',
name: 'John Doe'
},
{
id: 7,
email: 'sample#example.com',
name: 'Jane Smith'
}, ...
]
}, ...
]
Below is the code I currently have (Note: this code block is actually part of a larger async.series call):
sharedGames: function(next) {
db.games.index({userId: userId},
function(err, gamesData) {
if (err) {
logger.warn('Error retrieving games for userId %d', userId, err);
next(err, null);
} else {
gamesData = _.map(gamesData, function(game) {
return {
gameId: game.id,
description: game.description,
users: []
};
});
async.map(gamesdata,
function(item, callback) {
db.gameDetails.getGameUsers({gameId: item.gameId},
function(err, users) {
if (err) {
callback(err, null);
} else {
callback(null, _.without(users.userIds, Number(userId)));
}
}
);
},
function(err, results) {
if (err) {
next(null, null);
} else {
var flattenedResults = _.chain(results)
.flatten()
.uniq()
.value();
async.map(flattenedResults, function(user, callback) {
db.users.getById(user, function(err, userDetails) {
if (err) {
callback(err, null);
} else {
callback(null, _.pick(userDetails, 'id', 'email', 'name'));
}
});
}, function(err, users) {
if (err) {
next(null, null);
} else {
_.each(results, function(result, index) {
_.each(result, function(user) {
var customerDetails = _.find(customers, function(u) {
return u.id === user;
});
gamesData[index].users.push(userDetails);
});
});
console.log(gamesData);
next(null, gamesdata);
}
});
}
}
);
next(null, []);
}
}
);
}

So you can definitely accomplish what you're describing with async.auto. Check this from their docs:
async.auto({
get_data: function(callback){
console.log('in get_data');
// async code to get some data
callback(null, 'data', 'converted to array');
},
make_folder: function(callback){
console.log('in make_folder');
// async code to create a directory to store a file in
// this is run at the same time as getting the data
callback(null, 'folder');
},
write_file: ['get_data', 'make_folder', function(callback, results){
console.log('in write_file', JSON.stringify(results));
// once there is some data and the directory exists,
// write the data to a file in the directory
callback(null, 'filename');
}],
email_link: ['write_file', function(callback, results){
console.log('in email_link', JSON.stringify(results));
// once the file is written let's email a link to it...
// results.write_file contains the filename returned by write_file.
callback(null, {'file':results.write_file, 'email':'user#example.com'});
}]
}, function(err, results) {
console.log('err = ', err);
console.log('results = ', results);
});
So if you follow the same approach, you can get the array of game objects by injecting more info every call. You can also use async.waterfall. Hope it helps

Related

Callback NodeJS & insert data with mongoose

I know this topic as already asked many times before but I didn't find the right answer to do what I want.
Actually, I try to save two different list of JSON object in MongoDB via Mongoose. To perform both at the same time I use 'async'.
However, when I save it with the command insertMany() I get an error because he calls the callback of async before finishing the insertMany(). Therefore answer[0] is not defined.
What will be the proper way of doing it ?
Here is my code with the async:
const mongoose = require("mongoose");
const async = require("async");
const utils = require("../utils");
const experimentCreate = function(req, res) {
let resData = {};
let experimentList = req.body.experiment;
let datasetList = req.body.datasetList;
async.parallel(
{
dataset: function(callback) {
setTimeout(function() {
answer = utils.createDataset(datasetList);
callback(answer[0], answer[1]);
}, 100);
},
experiment: function(callback) {
setTimeout(function() {
answer = utils.createExp(experimentList);
callback(answer[0], answer[1]);
}, 100);
}
},
function(err, result) {
if (err) {
console.log("Error dataset or metadata creation: " + err);
sendJSONresponse(res, 404, err);
} else {
console.log("Experiment created.");
resData.push(result.dataset);
resData.push(result.experiment);
console.log(resData);
sendJSONresponse(res, 200, resData);
}
}
);
};
Then the two functions called createExp and createDataset are the same in another file. Like this:
const createDataset = function(list) {
let datasetList = [];
for (item of list) {
let temp = {
_id: mongoose.Types.ObjectId(),
name: item.name,
description: item.description,
type: item.type,
};
datasetList.push(temp);
}
Dataset.insertMany(datasetList, (err, ds) => {
if (err) {
console.log("Error dataset creation: " + err);
return [err, null];
} else {
console.log("All dataset created.");
return [null, ds];
}
});
};
There's a few problems with your code. For one, you're not returning anything in your createDataset function. You're returning a value in the callback of insertMany but it doesn't return that value to the caller of createDataset as it's within another scope. To solve this issue, you can wrap your Dataset.insertMany in a promise, and resolve or reject depending on the result of Data.insertMany like this:
const createDataset = function(list) {
let datasetList = [];
for (item of list) {
let temp = {
_id: mongoose.Types.ObjectId(),
name: item.name,
description: item.description,
type: item.type,
};
datasetList.push(temp);
}
return new Promise((resolve, reject) => {
Dataset.insertMany(datasetList, (err, ds) => {
if (err) {
console.log("Error dataset creation: " + err);
reject(err);
} else {
console.log("All dataset created.");
resolve(ds);
}
});
});
};
Now your return object is no longer going to be an array so you won't be able to access both the error and the result via answer[0] and answer[1]. You're going to need to chain a then call after you call createDataset and use callback(null, answer) in the then call (as that means createDataset executed successfully) or use callback(err) if createDataset throws an error like below:
dataset: function(callback) {
setTimeout(function() {
utils.createDataset(datasetList).then(answer => {
callback(null, answer);
}).catch(err => callback(err)); // handle error here);
}, 100);
}
Note: You'll most likely need to alter your createExp code to be structurally similar to what I've produced above if it's also utilizing asynchronous functions.

How to delete all documents in DocumentDB using NodeJS?

I have one collection user, which has many different properties.
Q.1 I want to run query with specific query and delete all those documents using nodejs, how can I do that?
Q.2 if I want to delete all documents using nodejs, how can I do this?
async.forEach(orders, function(order, callback) {
client.deleteDocument(colle._self,order, function(err, success) {
if (err) {
callback(err);
} else {
callback(null, success);
}
});
}, function(err, result) {
if (err) {
return respondFailed(res, { 'message': err }, 400);
} else {
respondSuccess(res, null, 0, { message: 'All Orders deleted.' });
}
});
I couldn't find a simple example of bulk delete.
Here is a similar solution working with the #azure/cosmos sdk:
const { resources: users } = await container.items
.query({
query: "SELECT * from u"
})
.fetchAll();
users.map(async usr => {
await container.item(usr.id, usr.pk).delete()
})
Thanks for all your concern. finally I found my mistake.
In my code I was passing coll._self collection link instead of docu._self link.
async.forEach(orders, function(order, callback) {
client.deleteDocument(order._self,order, function(err, success) {
if (err) {
callback(err);
} else {
callback(null, success);
}
});
}, function(err, result) {
if (err) {
return respondFailed(res, { 'message': err }, 400);
} else {
respondSuccess(res, null, 0, { message: 'All Orders deleted.' });
}
});

sailsjs where many to many relationship contains

Im trying to find all users that are doing a certain subject. Here is the relationship:
User.js
attributes: {
subjects: { collection: 'subject', via: 'users', dominant: true }, // Many to Many
levels: { collection: 'level', via: 'users', dominant: true } // Many to Many
}
Subject.js
attributes: {
users : { collection: 'user', via: 'subjects' } // Many to Many
}
Level.js
attributes: {
users : { collection: 'user', via: 'levels' } // Many to Many
}
I would like to be able to do a find on User and only return users that have a relationship to a specific subject. Something like this:
User.find({ subjects: { 'contains': 1 } })
.exec(function(err, results){
if(err) return res.serverError(err);
users = results;
return res.json(users);
});
I know that I can do it by doing:
Subject.findOne({id:1})
.populate('users')
.exec(function(err, results){
if(err) return res.serverError(err);
users = results.users;
return res.json(users);
});
However I do not want to do it this way as I may want to filter by more than just subject. For example using the above I may want to find all users that are doing subject 1 AND are doing level 2.
User.find({ subjects: { 'contains': 1 }, levels: { 'contains': 2 } })
.exec(function(err, results){
if(err) return res.serverError(err);
users = results;
return res.json(users);
});
I am using sails v0.12.3
There is no such mechanism as far as I know where you can search as you mentioned.
Though you can do it in following ways:
Using condition in populate
User.find()
.populate('subjects', {
name: 'subject-1'
})
.populate('levels', {
name: 'level-1'
})
.exec(function(err, user) {
if (err) {
// Error
}
sails.log.verbose(user);
});
Using async
async.auto({
subject: function(cb) {
Subject.findOne({
name: 'subject-1'
})
.exec(cb);
},
level: function(cb) {
Level.findOne({
name: 'level-1'
})
.exec(cb);
},
user: ['subject', 'level', function(cb, results) {
User.find({
subjects: results.subject.id,
levels: results.level.id
})
.exec(function(err, users) {
if (err) {
return cb(err);
}
sails.log.verbose(users); // Required user
});
}]
}, function(err, results) {
// Callback
});
Using .query() method where you have to write a JOIN query to fetch data.

Callback/Promises implementation for a boolean check

Currently I have the following callback system:
var saveTask = function(err, result) {
if (err) return callback(err, result);
var newid = mongoose.Types.ObjectId();
var task = new Task({
_id: newid,
taskname: req.body.name,
teamid: req.body.team,
content: req.body.content,
creator: req.user.userId
});
task.save(function (err) {
if (!err) {
log.info("New task created with id: %s", task._id);
return callback(null, task);
} else {
if(err.name === 'ValidationError') {
return callback('400', 'Validation error');
} else {
return callback('500', 'Server error');
}
log.error('Internal error(%d): %s', res.statusCode, err.message);
}
});
};
if (req.body.team) {
valTeam.isMember(req.body.team, req.user._id, function (err, done) {
if (err) {
saveTask('403', 'Not the owner or member of this team');
} else {
saveTask(null, true);
}
});
} else {
saveTask(null, true);
}
valTeam.isMember
exports.isMember = function(teamid, userid, callback) {
Team.find({'_id':teamid, $or:[{'creator': userid }, {'userlist': { $in : [userid]}}]}, function(err, result) {
if (err) return err;
console.log(result);
if (!result.length)
return callback('404', false);
else
return callback(null, true);
});
}
In short, if team is sent by POST, I'm checking if the user is member of that ID in valTeam.isMember. Am I using the correct syntax and best method to call back my saveTask function to save the task if the user is part of the team?
This code currently works, but I feel like there should be an easier way to do it? How could I use a promise to achieve the same thing?
Thanks in advance.
It's curious the fact that you create objects instead Schemas. However "every head is a different world", this is my way:
task.save(function(error, data){
if (error) {
trow error;
} else {
//Make whatever you want here with data
});

Sails.js: Inserting one-to-many associated models to MongoDB

I am trying to take advantage of the Waterline ORM in Sails.js to build an example app that has a model called 'Category'. Because a category can have multiple sub categories, I have the following one-to-many association for this model:
module.exports = {
adapter: 'mongo',
// adapter: 'someMysqlServer',
attributes: {
categoryTitle: {
type: 'string',
required: true
},
parentCat: {
model: 'category'
},
subCategories: {
collection: 'category',
via: 'parentCat'
},
articles: {
collection: 'article',
via: 'category',
required: false
}
}
};
In the CategoryController.js, I have the create method that first tries to see if the new category has a parent category assigned to it; however, I feel the code is quite messy, and the parentCat in Mongodb is always empty even if I tried to assign a parent category in the form submission. So I am wondering if this is the right way to do it:
create: function(req, res, next) {
var params = req.allParams();
// set parent category if exists
if (params.parentCat) {
Category.findOne({categoryTitle : params.parentCat})
.exec(function(err, category) {
if (err) {
return false; //not found
} else {
params.parentCat = category.id; //found the parent category
console.log('parent cat id is: ', category.id);
}
});
}
Category.create(params, function(err, newCategory) {
if (err) {
return next(err);
} else {
console.log('new category created');
}
console.log('successfully added the category: ' + newCategory.categoryTitle)
res.redirect('/category');
}); // create the category
}
The issue of your code is the callback.
I created a new version of code with the async feature (which is already in your sails app), hope it will help you.
create: function(req, res, next) {
var params = req.allParams();
async.waterfall([
function(callback) {
// set parent category if exists
if (params.parentCat) {
Category.findOne({
categoryTitle: params.parentCat
})
.exec(function(err, category) {
if (err) {
return false; //not found
}
params.parentCat = category.id; //found the parent category
console.log('parent cat id is: ', category.id);
callback(null, params);
});
} else {
callback(null, params);
}
},
function(params, callback) {
Category.create(params, function(err, newCategory) {
if (err) {
return next(err);
}
console.log('successfully added the category: ' + newCategory.categoryTitle);
callback(null, newCategory);
}); // create the category
}
], function(err, result) {
console.dir(result);
res.redirect('/category');
});
}

Categories