Do I use async.js in a bad way - async.js

I think I must be using Async in a bad way. The only way I managed to do it using Async is this:
var email = request.payload.email;
async.waterfall([
function (callback) {
async.parallel({
title : function (callbackWaterfall) {
async.waterfall([
async.apply(UserDao.findUser, email),
createTripTitle
], callbackWaterfall)
},
tripCount: TripDao.countTrips
}, callback)
},
function (results, callback) {
TripDao.createTrip(results.title, results.tripCount, callback);
}
], function (err, trip) {
resCallback(err, request, response, trip);
});
It removes the callback hell but still it is HELL to read it.
In a synchronous code I would have this simple code snippet:
var email = request.payload.email,
user = UserDao.findUser(email),
title = getTitle(user),
tripCount = countMyTrips(),
newTrip = TripDao.createTrip(title, tripCount);
Is there a way how to simplify my async.js example?

Use async.auto
var email = request.payload.email
async.auto({
user: async.apply(UserDao.findUser, email),
title: ['user', function (results, callback){
getTitle(results['user'], callback)
}],
tripCount: async.apply(countMyTrips),
newTrip: ['title', tripCount', function (results, callback) {
TripDao.createTrip(results['title'], results['tripCount'], callback);
}]
}, function (err, results) {
//all done
})

Due to #GolakSarangi and his async.auto solution I discovered async.autoInject that I find even more readable.
Here is my solution:
async.autoInject({
tripCount: TripDao.countTrips,
email: function(callback){ callback(null /*err*/, request.payload.email) },
user: ['email', UserDao.findUser],
title: ['user', createTripTitle],
trip: ['title', 'tripCount', TripDao.createTrip]
}, ['trip', function(err, trip){
resCallback(err, request, response, trip);
}]);

Related

wait for result from an async method

I'm trying to call an async method from for loop, but it doesn't wait for the result from that method.
Below is my code:
async function fetchActivityHandler (req, reply) {
esClient.search({
index: 'user_activity',
type: 'document',
body: {
_source : ["userId","appId","activity","createdAt","updatedAt"],
query: {
bool : {
must:[
{match : { 'userId': req.params.id }}
]
}
}
}
},async function (error, response, status) {
if (error){
console.log('search error: '+error)
}
else {
var activities = [];
//await Promise.all(response.hits.hits.map(async function(hit){
for (const hit of response.hits.hits) {
var activity = hit._source
var app = await fetchAppDetails(activity.appId);
console.log(app);
activity = {...activity,app : app}
activities.push(activity);
console.log(activity);
}
reply.status(200).send(activities);
}
});
}
async function fetchAppDetails (appId) {
esClient.get({
index: 'app',
type: 'document',
id: appId
}, function (err, response) {
console.log(response._source);
return (response._source);
});
}
What may be the problem. I'm using async and await, but it is not working.
Await works with promise. You should wrap your function with promise to get this work. Hope this will help you. Also you do not need to use async on fetchActivityHandler function. Only in the callback which you have already used.
function fetchAppDetails (appId) {
return new Promise((resolve,reject)=>{
esClient.get({
index: 'app',
type: 'document',
id: appId
}, function (err, response) {
if(err){
reject(err);
}
else{
resolve(response)
}
});
});
}

returning a result with node async waterfall in loopbackJS

I understand that with the recent version of async waterfall, cb is no longer available.
That being the case, how do give a remote method a response? I can't seem to find this explained anywhere.
To use an example from the async documentation.
async.waterfall([
function(callback) {
callback(null, 'one', 'two');
},
function(arg1, arg2, callback) {
// arg1 now equals 'one' and arg2 now equals 'two'
callback(null, 'three');
},
function(arg1, callback) {
// arg1 now equals 'three'
callback(null, 'done');
}
], function (err, result) {
// result now equals 'done'
// HOW DO I RETURN result TO THE CALLER OF THE REMOTE METHOD?
// HOW DO I RETURN result TO THE CALLER OF THE REMOTE METHOD?
// HOW DO I RETURN result TO THE CALLER OF THE REMOTE METHOD?
});
EDIT: Here is the actual event that I am trying to pass a response back from. In previous versions of async ,this was just done by passing it into cb().. but it appears this is no longer supported by async.
Ticket.addComment = function( id, comment, postedBy, cb ) {
async.waterfall([
//get ticket and add content
function(callback){
Ticket.findById( id, function( err, ticket ){
ticket.ticketComments.create({ "body": comment });
callback(null, ticket);
});
},
//update ticket isWith
function(ticket, callback){
ticket.save(null, {
"id": ticket.id,
"isWith": postedBy
});
callback(null,ticket);
}
], function( err, ticket ){
// I NEED TO RETURN "ticket" TO THE METHOD CALLER.. THIS USED TO BE DONE BY PASSING "ticket" INTO cb().
});
}
Ticket.remoteMethod('addComment', {
http: { verb: 'post'},
accepts: [
{arg: 'id', type: 'string', description: 'ticket id of the ticket the comment is to be added to'},
{arg: 'comment', type: 'string', description: 'the comment body'},
{arg: 'postedBy', type: 'string', description: 'Who posted the comment'}
],
returns: {arg: 'comment', root: true, type: 'application/json'}
});
var create = function (req, res) {
async.waterfall([
_function1(req),
_function2,
_function3
], function (error, success) {
if (error) { alert('Something is wrong!'); }
return alert('Done!');
});
};
function _function1 (req) {
return function (callback) {
var something = req.body;
callback (null, something);
}
}
function _function2 (something, callback) {
return function (callback) {
var somethingelse = function () { // do something here };
callback (err, somethingelse);
}
}
function _function3 (something, callback) {
return function (callback) {
var somethingmore = function () { // do something here };
callback (err, somethingmore);
}
}
Short answer: You don't. That is not the way to program asynchronously.
Is it possible to devise ways of making the caller wait for the async call to end and get its result. But doing that defeats the purpose of programming asynchronously.
So, if you have code that needs the results of the async.waterfall it MUST be placed on the final callback.
Here an example of how this is usually accomplished:
Ticket.addComment = function( id, comment, postedBy, cb ) {
async.waterfall([
//get ticket and add content
function(callback){
Ticket.findById( id, function( err, ticket ){
ticket.ticketComments.create({ "body": comment });
callback(null, ticket);
});
},
//update ticket isWith
function(ticket, callback){
ticket.save(null, {
"id": ticket.id,
"isWith": postedBy
});
callback(null,ticket);
}
], function( err, ticket){
// Instead of returning to the caller, use your result here
Ticket.remoteMethod('addComment', {
http: { verb: 'post'},
accepts: [
{arg: 'ticket', type: 'whatever', value: ticket, description: 'an example of how you could return the result'},
{arg: 'id', type: 'string', description: 'ticket id of the ticket the comment is to be added to'},
{arg: 'comment', type: 'string', description: 'the comment body'},
{arg: 'postedBy', type: 'string', description: 'Who posted the comment'}
],
returns: {arg: 'comment', root: true, type: 'application/json'}
});
cb(); // <-- not forgetting the callback
});
}
Note: On your code the methods Ticket.findById, ticket.save and Ticket.remoteMethod look synchronous. They basically defeat the purpose of having your code using async.waterfall: each function within waterfall() will block. Unless you find an async version of those methods you should remove the async altogether. It is just making your code more complicated without any significant benefit.

Added property don't get encoded with JSON

I am trying to get all course from the database and then add course_has_users if it exist.
The code works until I try to JSON encode it. Then I lose course_has_users when my angular front-end receives it.
Course.findAll({include: [
{model:Course_has_material},
{model:Course_has_competence},
{model:Organization},
{model:Module_type},
{model:Category},
{model:User, as:'mentor'}
],
where: {organization_id: user.organization_id}
}).then(function (courses) {
async.each(courses, function (course, callback) {
Course_has_user.findAll({
where: {user_id: user.user_id, course_id:course.id}
}, {}).then(function (course_has_user) {
course.course_has_users = course_has_user;
callback();
})
}, function (err) {
onSuccess(courses);
});
});
Route
.get(function (req, res) {
var course = Course.build();
course.retrieveAll(req.user, function (courses) {
if (courses) {
res.json(courses);
} else {
res.status(401).send("Courses not found");
}
}, function (error) {
res.send("Courses not found");
});
})
async.each will just iterate through it.
Use async.map and return course after setting course has users on it.
It should just work then. ;)
Course.findAll({include: [
{model:Course_has_material},
{model:Course_has_competence},
{model:Organization},
{model:Module_type},
{model:Category},
{model:User, as:'mentor'}
],
where: {organization_id: user.organization_id}
}).then(function (courses) {
async.map(courses, function (course, callback) {
Course_has_user.findAll({
where: {user_id: user.user_id, course_id:course.id}
}, {}).then(function (course_has_user) {
course.course_has_users = course_has_user;
callback(null, course);
})
}, function (err, _courses) {
// Note that we use the results passed back by async here!
onSuccess(_courses);
});
});
So you could also do, to simplify things a bit
Course.findAll({include: [
{model:Course_has_material},
{model:Course_has_competence},
{model:Organization},
{model:Module_type},
{model:Category},
{model:User, as:'mentor'}
],
where: {organization_id: user.organization_id}
})
.map(function (course) {
return Course_has_user.findAll({
where: {user_id: user.user_id, course_id:course.id}
}, {})
.then(function (course_has_user) {
course.course_has_users = course_has_user;
return course;
})
})
.then(onSuccess);
});
The problem was with Sequelize and fixed the issue by changing it's toJSON method and using async.map
Haven't tested with async.each but should work without map
instanceMethods: {
toJSON: function () {
var json = this.values;
json.course_has_users = this.course_has_users;
return json;
},
};
Retrieve method
retrieveMyCourses: function (user, onSuccess, onError) {
Course.findAll({include: [
{model:Course_has_material},
{model:Course_has_competence},
{model:Organization},
{model:Module_type},
{model:Category},
{model:User, as:'mentor'}
],
where: {organization_id: user.organization_id}
}).
then(function (courses) {
async.map(courses, function (course, callback) {
Course_has_user.findAll({
where: {user_id: user.user_id, course_id:course.id}
}, {}).then(function (course_has_user) {
course.course_has_users = course_has_user;
callback(null, course);
})
}, function (err, _courses) {
var test = JSON.parse(JSON.stringify(_courses));
onSuccess(_courses);
});
});
},
Route
router.route('/api/myCourses')
// Get all courses
.get(function (req, res) {
var course = Course.build();
course.retrieveMyCourses(req.user, function (courses) {
if (courses) {
res.json(courses);
} else {
res.status(401).send("Courses not found");
}
}, function (error) {
res.send("Courses not found");
});
});
Sequelize issue:
https://github.com/sequelize/sequelize/issues/549

Sequelize return response with nested loops

I am looping through and inserting elements in my addAll method. Once this has finished, I would like to return a response. My code currently works, but I am not sure how to get the response to happen.
Route
.post(function (req, res) {
var screening_module = Screening_module.build();
screening_module.screening_module = req.body.module;
screening_module.screening_module.organization_id = req.user.organization_id;
screening_module.addAll(function (success) {
res.json({message: 'screening_module created!'});
},
function (err) {
res.status(err).send(err);
}); });
AddAll
addAll: function (onSuccess, onError) {
var screening_module = this.screening_module;
screening_module.selectedUser.forEach(function (user) {
Screening_module.create({
organization_id: screening_module.organization_id,
supervisor: screening_module.supervisor.id,
name: screening_module.name,
description: screening_module.description,
deadline: screening_module.deadline,
category_id: screening_module.category.id,
screening_module_type_id: screening_module.type.id,
user_id: user.id
}).then(function (createdScreeningModule) {
Screening.create({
user_id: createdScreeningModule.dataValues.user_id,
screening_module_id: createdScreeningModule[null]
}).then(function (createdScreening) {
screening_module.selectedSkillsets.forEach(function (skillset) {
Screening_has_skillset.create({
screening_id: createdScreening[null],
skillset_id: skillset.id
})
})
})
})
});
},
Promise is your friend here
var Promise = require("bluebird");
addAll: function (onSuccess, onError) {
var screening_module = this.screening_module;
Promise.each(screening_module.selectedUser, function (user) {
return Screening_module.create({
organization_id: screening_module.organization_id,
supervisor: screening_module.supervisor.id,
name: screening_module.name,
description: screening_module.description,
deadline: screening_module.deadline,
category_id: screening_module.category.id,
screening_module_type_id: screening_module.type.id,
user_id: user.id
}).then(function (createdScreeningModule) {
return Screening.create({
user_id: createdScreeningModule.dataValues.user_id,
screening_module_id: createdScreeningModule[null]
}).then(function (createdScreening) {
return Promise.each(screening_module.selectedSkillsets, function (skillset) {
return Screening_has_skillset.create({
screening_id: createdScreening[null],
skillset_id: skillset.id
});
});
});
});
}).then(onSuccess, onError);
},
Untested, but hopefully give you an idea of how to handle. IMO, this kind of deep nesting is a code smell, and you might need to consider the design again

async.js methods inside async.js methods

Disclaimer: new to JS and everything around it. :)
I was wondering if we could nest async functions within one another
I have a waterfall method in node:
/**
* POST new travel wish
*/
router.post('/add', function(req, res) {
var db = req.db;
console.log(req.body);
// add properties: customer_id and timestamp
var date = new Date();
//...
var notif;
async.waterfall(
[
// find the User
function(next){
db.collection(userCollection).findOne({_id: new ObjectId(req.session.userID)}, next);
},
// insert a new travelwish
function(user, next){
notif = {
'username': user.username,
};
// TODO: add a async.parallel
db.collection('travelwishlist').insert(req.body, next);
},
// setup timer for processing and send confirmation
function(insertedTravelWish, next){
NotificationManager.sendConfirmationNotification(notif);
next(null, insertedTravelWish);
}
],
// waterfall callback --> send http response.
function (err, insertedTravelWish) {
res.send(
//if everything is ok the system sends the travel wish ID to the client otherwise error
(err === null) ? { msg: insertedTravelWish[0]["_id"] } : { msg: 'err' }
);
}
);
});
I want to insert multiple items instead of just one.
I think i can do this with parallel, but can I put it inside the existing waterfall?
Yep, you totally can do it, here is some piece of example
async.waterfall([
function (callback) {
callback(null, 1);
},
function (arg, callback) {
callback(null, arg1 + 1);
},
function (arg, callback) {
async.parallel([
function (_callback) { ... },
function (_callback) { ... },
], finalCallback);
}
]);
just a little tip, make sure you proivde different names for the callbacks (so you won't call the other one) if I've 2 nested functions that requires a callback most of the time I name them as callback _callback __callback

Resources