Callback errors in Async library - node.js

I'm using the async library together with mongoose as follows:
async.waterfall([
function(callback) {
async.map(new_tags, function(tag, callback) {
Tag.findOneAndUpdate(
{ '_id': tag._id },
{ '$setOnInsert': { '_id': tag._id, 'name': tag.name } },
{ 'upsert': true, 'new': true },
callback
);
}, callback);
}, function(tags, callback) {
for(var k = 0; k < tags.length; k++) {
res_tags.push(tags[k]._id);
}
callback(res_tags);
}
],
function(err, results) {
callback(err, results);
});
But I'm having doubts on how the catch the error at the end of async.waterfall... The code as it is will have in err, the actual resulting array (res_tags).
Can someone give me a hand?

You're not handling your callbacks appropriately. async uses error-first callbacks. This is an important concept in Node.js because this is considered the "best practice" for handling errors within a callback chain.
See this post on error-first callbacks and Node.js
See below for how to properly implement the callbacks within your code:
async.waterfall([
function(callback) {
var res
async.map(new_tags, function(tag, callback) {
Tag.findOneAndUpdate(
{ '_id': tag._id },
{ '$setOnInsert': { '_id': tag._id, 'name': tag.name } },
{ 'upsert': true, 'new': true },
function (err, doc) {
// If an error occurs, pass it back to our map callback.
if (err)
return callback(err, null);
// If there was no error return the doc
return callback(null, doc);
}
);
}, function (err, docs) {
// If an error occurred during map return it back to the waterfall
if (err)
return callback(err, null);
// Return back all docs
return callback(null, docs);
});
}, function(tags, callback) {
// For each tag push them to res_tags
async.each(tags, function(tag) {
res_tags.push(tags[k]._id);
}, function(err) {
if (err)
return callback(err, null);
return callback(null, res_tags);
});
}
],
function(err, results) {
// If an error happened during any execution in waterfall catch it and handle it
if (err)
// Error handling
else
return results; // No error, return our results
});

The first parameter of each function callback in the waterfall should be an Error object or null if there were no errors.
callback(res_tags);
Should be changed to:
callback(null, res_tags);
From the documentation (https://github.com/caolan/async#waterfall):
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'
});

Related

How to remove object taking into account references in Mongoose Node.js?

This is my MongoDB schema:
var partnerSchema = new mongoose.Schema({
name: String,
products: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Product'
}]
});
var productSchema = new mongoose.Schema({
name: String,
campaign: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Campaign'
}
]
});
var campaignSchema = new mongoose.Schema({
name: String,
});
module.exports = {
Partner: mongoose.model('Partner', partnerSchema),
Product: mongoose.model('Product', productSchema),
Campaign: mongoose.model('Campaign', campaignSchema)
}
And I wondering how can I remove object from any document taking into account references (maybe should I use somehow populate from mongoose)? For example if I will remove Product then I assume that I will remove also ref ID in Partner and all Campaigns which belong to this Product.
At the moment I removing in this way:
var campSchema = require('../model/camp-schema');
router.post('/removeProduct', function (req, res) {
campSchema.Product.findOneAndRemove({ _id: req.body.productId }, function (err, response) {
if (err) throw err;
res.json(response);
});
});
However in mongo still left references.
You would have to nest your calls to remove the product id from the other model. For instance, in your call to remove the product from the Product
collection, you could also make another call to remove the ref from the Partner model within the results callback. Removing the product by default will remove its refs to the Campaign Model.
The following code shows the intuition above:
var campSchema = require('../model/camp-schema');
router.post('/removeProduct', function (req, res) {
campSchema.Product.findOneAndRemove({ _id: req.body.productId }, function (err, response) {
if (err) throw err;
campSchema.Partner.update(
{ "products": req.body.productId },
{ "$pull": { "products": req.body.productId } },
function (err, res){
if (err) throw err;
res.json(res);
}
);
});
});
To remove the associated campaigns then you may need an extra remove operation that takes in the associated campaign id fro a given product id. Consider the following dirty hack which may potentially award you a one-way ticket to callback hell if not careful with the callback nesting:
router.post('/removeProduct', function (req, res) {
campSchema.Product.findOneAndRemove(
{ _id: req.body.productId },
{ new: true },
function (err, product) {
if (err) throw err;
campSchema.Partner.update(
{ "products": req.body.productId },
{ "$pull": { "products": req.body.productId } },
function (err, res){
if (err) throw err;
var campaignList = product.campaign
campSchema.Campaign.remove({ "_id": { "$in": campaignList } })
.exec(function (err, res){
if (err) throw err;
res.json(product);
})
}
);
}
);
});
Although it works, the above potential pitfall can be avoided by using async/await or the async library. But firstly, to give you a better understanding of the using multiple callbacks with the async module, let's illustrate this with an example from Seven Things You Should Stop Doing with Node.js of multiple operations with callbacks to find a parent entity, then find child entities that belong to the parent:
methodA(function(a){
methodB(function(b){
methodC(function(c){
methodD(function(d){
// Final callback code
})
})
})
})
With async/await, your calls will be restructured structured as
router.post('/removeProduct', async (req, res) => {
try {
const product = await campSchema.Product.findOneAndRemove(
{ _id: req.body.productId },
{ new: true }
)
await campSchema.Partner.update(
{ "products": req.body.productId },
{ "$pull": { "products": req.body.productId } }
)
await campSchema.Campaign.remove({ "_id": { "$in": product.campaign } })
res.json(product)
} catch(err) {
throw err
}
})
With the async module, you can either use the series method to address the use of callbacks for nesting code of multiple methods which may result in Callback Hell:
Series:
async.series([
function(callback){
// code a
callback(null, 'a')
},
function(callback){
// code b
callback(null, 'b')
},
function(callback){
// code c
callback(null, 'c')
},
function(callback){
// code d
callback(null, 'd')
}],
// optional callback
function(err, results){
// results is ['a', 'b', 'c', 'd']
// final callback code
}
)
Or the waterfall:
async.waterfall([
function(callback){
// code a
callback(null, 'a', 'b')
},
function(arg1, arg2, callback){
// arg1 is equals 'a' and arg2 is 'b'
// Code c
callback(null, 'c')
},
function(arg1, callback){
// arg1 is 'c'
// code d
callback(null, 'd');
}], function (err, result) {
// result is 'd'
}
)
Now going back to your code, using the async waterfall method you could then restructure your code to
router.post('/removeProduct', function (req, res) {
async.waterfall([
function (callback) {
// code a: Remove Product
campSchema.Product.findOneAndRemove(
{ _id: req.body.productId },
function (err, product) {
if (err) callback(err);
callback(null, product);
}
);
},
function (doc, callback) {
// code b: Remove associated campaigns
var campaignList = doc.campaign;
campSchema.Campaign
.remove({ "_id": { "$in": campaignList } })
.exec(function (err, res) {
if (err) callback(err);
callback(null, doc);
}
);
},
function (doc, callback) {
// code c: Remove related partner
campSchema.Partner.update(
{ "products": doc._id },
{ "$pull": { "products": doc._id } },
function (err, res) {
if (err) callback(err);
callback(null, doc);
}
);
}
], function (err, result) {
if (err) throw err;
res.json(result); // OUTPUT OK
});
});

Node.js MongoDB collection.update() callback never called

Can anyone explain why the callback below is never getting called ?
The update works by the callback is never called. Did I miss something ?
collection.update({_id:partner._id},
{$set: {
groups: newGroups
}
},
{ upsert: false, w: 1 },
function(err, status){
console.log("update callback ");
if (err){
console.log("Error updating "+err.message);
callback(false);
} else {
console.log("Record updated as "+JSON.stringify(status));
callback(true);
}
}
);
You can wrap this in a function with a callback as a param:
//Except for callback as a parameter, every parameter is not compulsory
function yourFunctionName(id, newGroups, callback) {
collection.update({
_id: partner._id
}, {
$set: {
groups: newGroups
}
}, {
upsert: false,
w: 1
},
function(err, status) {
console.log("update callback ");
if (err) {
console.log("Error updating " + err.message);
callback(false);
} else {
console.log("Record updated as " + JSON.stringify(status));
callback(true);
}
}
)
}
Then call the function like this:
yourFunctionName(params, function(result) {
//Do anything with the result
});
If you want to understand how callback functions work, this might help you.
For example in node(sailsJs)
Model.update({id:'xxxxxxxxxxxxxxxx'},{name:'Flynn'}).exec(function afterwards(err, updated){
if (err) {
//handle error here!
return;
}
console.log('Updated user to have name ' + updated[0].name);
});

How to stop executing waterfall on error in async of node.js?

I am using async module with waterfall method.
async.waterfall([
function(callback) {
...
callback(err);
},
function(result, callback) {
console.log("This function should not be executed");
}
],
function(err) {
if (err) {
next(err);
return;
}
}
);
But the second function always execute. How to prevent it?
Try adding a return
async.waterfall([
function(callback) {
...
return callback(err); //note return here
},
function(result, callback) {
console.log("This function should not be executed");
}
],
function(err) {
if (err) {
next(err);
return;
}
}
);

Nodejs Angularjs Mongoose Query inside async map

From the codes below, I can add to my database. However, when I am trying to mongoose-find to look for the database, I am not getting any value. can anybody help? I want to res.json the result.
app.post('/api/infosave', function(req,res){
async.series([function (cb){
dbinfo.remove({}, function(err,result){
if (err) throw err;
cb(null);
});
}, function (cb){
var bookNum = [];
for (var i = 0; i < req.body.numBooks; i++) {
bookNum.push(i+1)
}
async.map(bookNum, function(num, cb) {
dbinfo.create({
numBooks: num,
done: false
}, cb);
}, cb);
}, function (cb){
dbinfo.find({},function(err,result){
if (err)
res.send(err);
res.json(result);
console.log(result);
cb(null);
});
}], function(error, results) {
});
});
As I said in my comment: you're calling res.json() inside one of the series function, and that won't work. What you should do is pass the result of dbinfo.find() to the local callback:
dbinfo.find({},function(err,result){
if (err)
cb(err);
cb(null, result);
});
, and in the async.series callback, call res.json():
...
}], function(error, results) {
if (error) return res.json({ error: error });
return res.json(results[2]); // the result you want should be third element of results
});

Node.js : res.redirect("back") got Error('Can\'t set headers after they are sent.');

I have a little problem on an express.js app.
Can't redirect back after several actions with callbacks.
My applications works with objects called "markers" and they are rated by "sub categories"
The goal of these actions is to merge 2 or more subcategories, move all markers from old to the new subcategoy, and finally, delete the old sub category.
Here is the code :
The action called after checked 2 or more subcategories :
exports.postMergeSubCategories = function(req, res) {
"use strict";
var data = {};
data.subCategories = req.body.subCategoriesChecked;
data.enName = req.body.subCategory.enName;
data.frName = req.body.subCategory.frName;
data.deName = req.body.subCategory.deName;
if (data.subCategories.length > 1) {
sscategoryMapper.merge(data, function(err, sscategory) {
if (err) return console.log(err);
console.log ('Sub categories merged !');
req.flash('mergeMessage', 'Merge completed!');
res.redirect("back");
});
} else { // error
console.log('Error while merge sub categories. It seems the number of sub categories checked less 2');
req.flash('mergeMessage', 'An error occured whil merge');
res.redirect("back");
}
};
The function sscategoryMapper.merge :
module.exports.merge = function(data, callback) {
async.waterfall([
function(callback){ // create new sub category
save(data, function(err, sscategory) {
if (err) return callback(err);
callback(null, sscategory._id);
});
},
function(sscategoryId, callback){ // update marker2sscategory
async.each(data.subCategories.split('|'), function(oldSscategoryId, err) { // for each subcategories, update markers2sscategory and remove the old subcategory
async.waterfall([
function(callback) { // update maker2sscategory to set the new sscategoryId to all target markers
marker2sscategoryMapper.update(sscategoryId, oldSscategoryId, function(err) {
if (err) return callback(err);
callback(null, oldSscategoryId);
});
},
function(oldSscategoryId, callback) { // delete the old sscategory
remove(oldSscategoryId, function(err) {
if (err) return callback(err);
callback();
});
}
], function(err) {
if (err) return callback(err);
callback();
});
}, function(err) {
callback();
});
callback(null, sscategoryId);
}
], function (err, result) {
if (err) return callback(err);
callback(null, result);
});
};
UPDATE
First problem here : I called 2 times the callback ... This piece of code moved to that :
}, function(err) {
callback(null, sscategoryId);
});
}
], function (err, result) {
if (err) return callback(err);
callback(null, result);
});
};
UPDATE END
The function marker2sscategoryMapper.update :
module.exports.update = function(to, from, callback) {
// update marker2sscategory with the new subcategory
dbMarker2sscategory.update({'_sscategory' : from}, {
'_sscategory' : to
}, {multi: true}, function(err) {
if (err) return callback(new Error(err));
callback(null, to);
});
}
And the function remove :
var remove = module.exports.remove = function(id, callback) {
dbSscategory.remove({'_id' : id}, function(err) {
if (err) return callback(new Error(err));
callback(null, id);
});
};
If I comment "res.redirect("back")", it will works.
Else, the error is : "Error('Can\'t set headers after they are sent.');" 2 times.
I read that one of the causes can be duplicate callbacks ... But I don't see that in my code.
Appreciate your help.

Resources