async.parallel inside async.series - node.js

I have this async.parallel functionality inside an aysnc.eachSeries call.
I hardcoded an error so I could pass it, to see if it was behaving the way I thought. For some reason, when I pass an error, it doesn't get thrown in the final callback named "doneWithSeries".
async.eachSeries(jsonDataArr, function iterator(item, callback) {
async.parallel([
function (cb) {
if (item.hasOwnProperty('event.type')) {
var event_type = item['event.type'];
delete item['event.type'];
try {
var json = JSON.stringify(item);
}
catch (err) {
throw err;
}
fs.writeFile('./enriched_data/' + event_type + '.json', json, function (err) {
if (err) {
cb(err);
}
else {
cb(null);
}
});
}
},
function (cb) {
if (item.hasOwnProperty('status_desc')) {
var status_desc = item['status_desc'];
delete item['status_desc'];
try {
var json = JSON.stringify(item);
}
catch (err) {
throw err;
}
fs.writeFile('./enriched_data/' + status_desc + '.json', json, function (err) {
if (err) {
cb(err);
}
else {
cb(null);
}
});
}
}
],
function doneWithParallel(err) {
callback(new Error('throw this baby')); //shouldn't the first incident of error pass the error straight to the doneWithSeries callback below?
})
},
function doneWithSeries(err) {
if (err) {
throw err;
}
else {
console.log('success');
}
});
here is a distilled version of the code without anything unnecessary:
var async = require('async');
async.eachSeries(['1', '2'], function (item, callback) {
async.parallel([
function (cb) {
setTimeout(function () {
cb(null, 'one');
}, 200);
},
function (cb) {
setTimeout(function () {
cb(null, 'two');
}, 100);
}
],
function doneWithParallel(err, results) {
console.log('results', results);
callback(new Error('duh'));
})
},
function doneWithSeries(err) {
if (err)
throw err;
});
indeed that works. can't figure out why my code above doesn't, accept perhaps that the array could be empty even though when I run my code the success message gets logged...weird.

I think that's expected behavior if your list is empty. async will always call the final callback with no error even if there is no input list.

Related

Function returning undefined in try block

My code is currently returning as 'undefined';
function findDoc(rowObj) {
Database.findOne(
{ 'name': rowObj.name },
'_id, foreign_id'
).exec((err, docs) => {
if (err) {
throw err;
} else {
return docs;
});
}
try {
console.log('before');
let searchDoc = await findDoc(rowObj);
console.log(searchDoc);
console.log('after');
} catch (err) {
console.log(err);
}
This outputs;
before
undefined
after
whereas if i change the findDoc function to console.log(docs); rather than return docs i get;
before
CORRECT OBJECT
after
How can i fix this to get the second result using the first structure where it returns to the main try catch block?
Thanks
You should either wrap your function with async or use a callback as follows :
function findDoc(rowObj,callback) {
Database.findOne(
{ 'name': rowObj.name },
'_id, foreign_id'
).exec((err, docs) => {
if (err) {
throw err;
} else {
callback(docs);
});
}
try {
console.log('before');
findDoc(rowObj,(searchDoc) => {
console.log(searchDoc);
console.log('after');
});
} catch (err) {
console.log(err);
}

async waterfall callback was already called error in nodejs

I am newbee to nodejs. I have been scratching my head for long time on why below code throwing error.I found some similar in stack overflow, but couldn't get help in finding the bug.
var albums_coll,photos_coll;
async.waterfall([
function(cb){
MongoClient.connect(url,
(err,dbase)=>{
if(err) {
console.log('bad');
process.exit(-1);
}
console.log("I have a connection!");
db=dbase;
cb(null);
}
);
},
function(cb){
db.collection("albums",cb);
},
function(album_obj,cb){
albums_coll = album_obj;
db.collection("photos",cb);
},
function(photos_obj,cb){
photos_coll = photos_obj;
cb(null);
},
function(cb){
albums_coll.InsertOne(a1,cb);
},
function(inserted_doc,cb){
console.log("I have Inserted a document!!");
console.log(inserted_doc);
cb(null);
}
],
function(err,results){
console.log("Done!!!");
console.log(err);
console.log(results);
db.close();
});
Please suggest!!
the following is the error its showing.
I have a connection!
C:\Users\thathine\NodeLive\Chapter08\mongotest\node_modules\mongodb\lib\mongo_client.js:433
throw err
^
Error: Callback was already called.
at C:\Users\thathine\NodeLive\Chapter08\mongotest\node_modules\async\dist\async.js:903:32
at Db.collection (C:\Users\thathine\NodeLive\Chapter08\mongotest\node_modules\mongodb\lib\db.js:466:27)
.
.
.
The callback() function you are passing into the db.collection() function is being called twice. The first time this happens is inside the MongoDB library while invoking db.collection(). It is happening in the following block - I added a comment pointing to line 466 as shown in the stack:
if(options == null || !options.strict) {
try {
var collection = new Collection(this, this.s.topology, this.s.databaseName, name, this.s.pkFactory, options);
if(callback) callback(null, collection);
return collection;
} catch(err) {
// if(err instanceof MongoError && callback) return callback(err);
if(callback) return callback(err); // <-- Line 466 - first time callback is called
throw err;
}
}
The second time it gets called, is by the async library when it needs to determine if it has to execute the next task in the array or invoke the final callback; by then, the callback is already null. The error you're seeing is being raised in the onlyOnce() function that is part of the async library:
function onlyOnce(fn) {
return function() {
if (fn === null) throw new Error("Callback was already called.");
var callFn = fn;
fn = null;
callFn.apply(this, arguments);
};
}
So the following code will raise the error:
async.waterfall([
function(callback) {
MongoClient.connect('mongodb://localhost:27017/test', (error, db) => {
if (error) {
console.error(error);
process.exit(-1);
}
callback(null, db);
});
},
function(db, callback) {
console.log('Querying albums collection...');
// This will cause the exception
db.collection('albums', callback);
},
function(db, albums, callback) {
console.log('Got albums...');
console.log('Querying photos collection...');
db.collection('photos', (error, photos) => {
if (error) {
return callback(error);
}
callback(null, db, albums, photos);
});
},
function(db, albums, photos, callback) {
console.log('Got photos...');
callback(null, 'DONE');
}
], function (error, results) {
console.error(error);
console.log(results);
process.exit(0);
});
While this version of the code will execute as expected:
async.waterfall([
function(callback) {
MongoClient.connect('mongodb://localhost:27017/test', (error, db) => {
if (error) {
console.error(error);
process.exit(-1);
}
callback(null, db);
});
},
function(db, callback) {
console.log('Querying albums collection...');
db.collection('albums', (error, albums) => {
if (error) {
return callback(error);
}
callback(null, db, albums);
});
},
function(db, albums, callback) {
console.log('Got albums...');
console.log('Querying photos collection...');
db.collection('photos', (error, photos) => {
if (error) {
return callback(error);
}
callback(null, db, albums, photos);
});
},
function(db, albums, photos, callback) {
console.log('Got photos...');
callback(null, 'DONE');
}
], function (error, results) {
console.error(error);
console.log(results);
process.exit(0);
});
/* Output:
Querying albums collection...
Got albums...
Querying photos collection...
Got photos...
null
DONE
*/
Hope this helps!

How to return value in collection.find({})

var shortLinks = [];
Link.find({}, function (err, links) {
if (err) {
console.log(err);
} else {
links.map(link => {
shortLinks.push(link.shortLink);
});
}
console.log(shortLinks);//shortLinks has values, all okey
});
console.log(shortLinks); //shortLinks is empty
i need to use shortLinks after Link.find({}) but array is empty.
Need to return shortLinks.
Callbacks. The function(err, links) is called asynchronously, so shortLinks isn't populated until that function is called. Your bottom console.log is being called first, due to how callbacks work.
https://developer.mozilla.org/en-US/docs/Mozilla/js-ctypes/Using_js-ctypes/Declaring_and_Using_Callbacks
need to use promise:
const shortLinks = [];
const getShortLinks = Link.find({}, function (err, links) {
if (err) {
console.log(err);
} else {
links.map(link => {
shortLinks.push(link.shortLink);
});
}
});
getShortLinks.then(function(links){
console.log(shortLinks);
}, function(err){
console.log(err);
});

No duplicate one try/catch by waterfall function (using async waterfall)

I'm using async waterfall and i've got a question about try/catch error.
I wan't to dodge this syntax method with one global try/catch and no duplicate a try/catch by function :
async.waterfall([
function (callback) {
try {
this.foo() // Ok
this.bar() // Method bar doesn't exist so without try/catch node will crash
} catch(ex) {
//print err
}
},
function (callback) {
try {
//Again & again
} catch(ex) {
//print err
}
}
//... function() with try catch ...
], function(err, result) {
//Do something
})
If you seek a more robust error handling system; try this:
var async = require('async');
function callbackErrorHandler(fn, callback) {
try {
fn()
} catch (err) {
callback(err);
}
}
async.waterfall([
function(callback) {
callbackErrorHandler(function() {
throw new Error('error1');
}, callback);
},
function(callback) {
callbackErrorHandler(function() {
throw new Error('error2');
}, callback);
}
],
//final
function(err, result) {
console.log(err);
});
/**
* Using Promises
*/
var async = require('async');
//promises
var Promises = require('bluebird');
function callbackErrorHandler(fn) {
return new Promises(function(resolve, reject) {
try {
resolve(fn()); //should return something
} catch (err) {
reject(err);
}
});
}
async.waterfall([
function(callback) {
callbackErrorHandler(function() {
throw new Error('error1');
})
.error(callback)
},
function(callback) {
callbackErrorHandler(function() {
throw new Error('error2');
})
.error(callback)
}
],
//final
function(err, result) {
console.log(err);
});
This script no longer print in final callback and crash node server :
Test this and try to rename test() by testt()
'use strict'
var async = require('./lib/node_modules/async')
function test()
{
console.log("hi !")
}
async.waterfall([
function(callback){
callback(null)
},
function(callback){
test()
callback(null, null)
}], function (err, result) {
if (err)
{
console.log("got an error !")
}
else
{
console.log("everyting is ok")
}
}
)

async forEach loops making api calls

I have a function that makes an api call and a second function that loops through the data of the first function and makes an api call each iteration. I'm trying to use the async library to make this happen but the 2nd function is still running asynchronously instead of waiting to finish. So I end up running function 1 runs, function 2 starts, but final callback runs before function 2 finishes.
async.series([
function (callback) {
//api call
getShelves.execute(function (err, shelves) {
if (err) { return callback(err); }
async.forEach(shelves.items, function (shelf, callback) {
var shelfObj = {id: shelf.id, title: shelf.title, books: []};
bookShelves.push(shelfObj);
callback();
});
//sort numerically to make placing books easier
bookShelves.sort(function (a, b) {return a.id - b.id; });
callback();
});
},
function (callback) {
async.forEach(bookShelves, function (shelf, callback) {
//api call
getBooks.execute(function (err, books) {
if (err) { return callback(err); }
if (books.items) {
async.forEach(books.items, function (book, callback) {
var bookObj = {title: book.volumeInfo.title};
bookShelves[shelf.id].books.push(bookObj);
callback();
});
}
callback();
});
});
callback();
}
], function (err) {
if (err) { console.log('error'); }
res.render('collection', { shelves: bookShelves });
});
});
EDIT: Working now thanks guys
function (callback) {
async.forEach(bookShelves, function (shelf, callback) {
getBooks.execute(function (err, books) {
if (err) { return callback(err); }
if (books.items) {
async.forEach(books.items, function (book, callback) {
var bookObj = {title: book.volumeInfo.title};
bookShelves[shelf.id].books.push(bookObj);
console.log(book.volumeInfo.title);
//callback to continue book loop
callback();
}, function () {
//callback to continue shelf loop
callback();
});
}else{
callback();
}
});
}, function () {
//callback to end function and move to next. However this is never reached
callback();
});
}
The second function in your series calls its callback immidiately, not waiting until async.forEach iteration finishes. Instead, try this to call it afterwards:
function (callback) {
async.forEach(bookShelves, function (shelf, callback) {
//api call
//... skipped ...
}, function() {
callback();
});
}
function loadShelf(shelf, callback) {
//_.pick is handy for this FYI
var shelfObj = {id: shelf.id, title: shelf.title};
//presumably your getBooks call takes a shelf id to relate the
//getBooks is an asynchronous DB or API call presumably
getBooks(shelf.id, function (error, books) {
if (error) {
callback(error);
return;
}
//This is an in-memory array. No async needed.
shelfObj.books = books.map(function (book) {
return {title: book.volumeInfo.title};
});
callback(null, shelfObj);
});
}
getShelves.execute(function (error, dbShelves) {
if (error) {
res.render('error', error); //pseudo-code error handling
return;
}
async.each(dbShelves, loadShelf, function (error, fullShelves) {
if (error) {
res.render('error', error); //pseudo-code error handling
return;
}
//sort numerically to make placing books easier
var sortedShelves = fullShelves.sort(function (a, b) {return a.id - b.id; });
res.render('collection', { shelves: sortedShelves });
});

Resources