0

I basically need to make about 3 calls to get the data for a json object.. It basically JSON array of JSON object which have some attributes, one of which is an array of other values selected using a second query, then that one also has an array inside which is selected with another db call.

I tried using asyn.concatSeries so that I can dig down into the bottom call and put together all the information I collected for one root json object but that's creating a lot of unexpected behaviour..

Example of JSON

[
    {
       "item" : "firstDbCall"
       "children" : [ {
                       "name" : "itemDiscoveredWithSecondDBCall"
                       "children" : [ itemsDiscoveredwith3rdDBCall]
                      },
                    ]
    }
]

This is really difficult using node.js. I really need to figure out how to do this properly since I have to do many of these for different purposes.

EDIT This is the code i have. There's some strange behaviour with async.concatSeries. The results get called multiple times after each one of the functions finish for each array. So i had to put a check in place. I know it's very messy code but i've been just putting band-aids all over it for the past 2 hours to make it work..

console.log("GET USERS HAREDQARE INFO _--__--_-_-_-_-_____");
var query = "select driveGroupId from tasks, driveInformation where agentId = '" 
        + req.params.agentId + "' and driveInformation.taskId = tasks.id order by driveInformation.taskId desc;";

connection.query(query, function(err, rows) {
    if (rows === undefined) {
        res.json([]);
        return;
    } 
    if(rows.length<1) { res.send("[]"); return;}
    var driveGroupId = rows[0].driveGroupId;
    var physicalQuery = "select * from drives where driveGroupId = " + driveGroupId + ";";
    connection.query(physicalQuery, function(err, rows) {
        console.log("ROWSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS");
        console.log(rows);
        async.concatSeries(rows, function(row, cb) {
            console.log("-------------------------------SINGLE ROW-------------------------------------");
            console.log(row);
            if(row.hasLogicalDrives != 0) {
                console.log("HAS LOGICAL DRIVES");
                console.log(row.id); 
                var query = "select id, name from logicalDrives where driveId = " + row.id;
                connection.query(query, function(error, drives) {
                    console.log("QUERY RETURNED");
                    console.log(drives);
                    parseDriveInfo(row.name, row.searchable, drives, cb);

                });
            }
            else
                var driveInfo = { "driveName" : row.name, "searchable" : row.searchable};
                console.log("NO SUB ITEMS");
                cb(null, driveInfo);
        }, function(err, results) {
            console.log("GEETTTTINGHERE");
            console.log(results);
            if(results.length == rows.length) {
                console.log("RESULTS FOR THE DRIVE SEARCH");
                console.log(results);
                var response = {"id": req.params.agentId};
                response.driveList = results;
                console.log("RESPONSE");
                console.log(response);
                res.json(response);
            }
        });
    });
});     

 };

 parseDriveInfo = function(driveName, searchable, drives, cb) {
async.concatSeries(drives, function(drive,callback) {
    console.log("SERIES 2");
    console.log(drive);
    console.log("END OF DRIVE INFO");
    var query = "select name from supportedSearchTypes where logicalDriveId = " + drive.id;
    connection.query(query, function(error, searchTypes) {
        drive.searchTypes = searchTypes;
        var driveInfo = { "driveName" :driveName,
            "searchable" : searchable,
            "logicalDrives" : drive
        };

        callback(null, driveInfo);

    });
}, function (err, results) {
    console.log("THIS IS ISISIS ISISISSISISISISISISISISISIS");
    console.log(results);
    if(results.length === drives.length) {
        console.log("GOTHERE");
        cb(null, results);
    }
});     

}

Tolga E
  • 12,188
  • 15
  • 49
  • 61
  • Can your provide information on the library your using and a little code sample... – MobA11y Jun 21 '13 at 18:41
  • I added the code i've been trying with.. Thanks for the help – Tolga E Jun 21 '13 at 18:46
  • I typed up an answer, involving what I think is a different, but similar scenario, let me know if it helps! Otherwise I will dig in. – MobA11y Jun 21 '13 at 18:52
  • I see something in your code that looks suspect, after this line: parseDriveInfo(row.name, row.searchable, drives, cb); there is an else statment, but it doesn't have brackets... Is this correct? – MobA11y Jun 21 '13 at 19:01
  • @ChrisCM Insteresting, i haven't noticed that before.. I checked the brackets like 3 times but i guess this one kept slipping away – Tolga E Jun 21 '13 at 19:12
  • @ChrisCM I fixed the brackets but some results are still duplicated – Tolga E Jun 21 '13 at 19:14
  • You should get a linter! Also, you shouldn't combine the use of async.series with other async logic. For example in your parseDriveInfo function you use async.series, but within that there is an asynchronous function... You might as well not use async here at all. I recommend switching completely to one approach, or completely to another. – MobA11y Jun 21 '13 at 19:14
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/32182/discussion-between-tolga-e-and-chriscm) – Tolga E Jun 21 '13 at 19:16

3 Answers3

1

Getting good enough with async to use exactly the right combination of methods under the right circumstances takes a fair amount of experience. Most likely your case in particular can be handled with async.waterfall if its query1 then query2(dataFoundByQuery1) then query3(dataFoundByQuery2). But depending on the circumstances you need to mix and match async methods appropriately and sometimes have 2 levels - for example a "big picture" async.waterfall where some of the steps in the waterfall do async.parallel or async.series as needed. I've never used async.concat and given your needs I think you have chosen the wrong method. The workhorses are async.each, async.eachSeries, async.waterfall, and async.map, at least for the web app & DB query use cases I mostly encounter, so make sure you really have those understood before exploring the more specific convenience methods.

Peter Lyons
  • 142,938
  • 30
  • 279
  • 274
  • I see but the problem with waterfall and stuff is that I don't know the exact number of results i will get, so i have a dynamic number of functions I have to execute.. Using concat handles this because i just give it an array of values to call a certain function that many times.. Is there a way I can give async.series an array of indeterminate length of functions? – Tolga E Jun 21 '13 at 19:24
  • Also i couldn't use async.eachSeries because that doesn't pass any data down to the final result.. I need to pass the data because after collecting all that data, i will need to put them into a json object to send it back to the requestor.. Am i missing something here? – Tolga E Jun 21 '13 at 19:29
  • You don't have an indeterminate length of FUNCTIONS, you have an indeterminate length of INVOCATIONS. Both concat and series (and any of the functions that take an array as the first argument) deal with arrays of arbitrary length. You don't need to know the exact number of results. I think you need to spend some more time with async and some experiments and tutorials. It can do what you need but at the moment you need a tutorial not just a specific answer. – Peter Lyons Jun 21 '13 at 21:19
0

EDIT: This is a more in depth example based on use of the connection library you seem to be using. Please note, some of this is javascript psuedo code. Things like adding objects to the resultsArray are clearly not complete, the only thing I took time to make sure was correct is the "flow of logic" as it pertains to callbacks. Everything else is for you to implement. In order to support multiple calls to the same callback function and maintain state from call to call, the best way is to wrap the set of callbacks in a closure. This allows the callbacks to share some state with the main event loop. This allows you to pass arguments to the callbacks, without actually having to pass them as arguments, much like class variables in c++, or even globals in javascript, but we haven't poluted the global scope :)

function queryDataBase(query) {
//wrap the whole query in a function so the callbacks can share some
//variables with similar scope.  This is called a closure

    int rowCounter = 0;
    var dataRowsFromStep2;
    var resultsArray = {};

    connection.query(query, dataBaseQueryStep2);

    function dataBaseQueryStep2(err, rows) {
        //do something with err and rows
        dataRowsFromStep2 = rows;

        var query = getQueryFromRow(dataRowsFromStep2[rowCounter++]);//Always zero the first time.  Might need to double check rows isn't empty!

        connection.query(query, dataBaseQueryStep3);
    }

    function dataBaseQueryStep3(err, rows) {
        //do something with err and rows

        if(rowCounter < dataRowsFromStep2.size) {
            resultsArray.add(rows);//Probably needs to be more interesting, but you get the idea
            //since this is within the same closure, rowCounter maintains it's state
            var query = getQueryFromRow(dataRowsFromStep2[rowCounter++]);

            //recursive call query using dataBaseQueryStep3 as it's callback repeatedly until
            //we run out of rows to call it on.
            connection.query(query, dataBaseQueryStep3)
        } else {
            //when the if statement fails we have no more rows to run queries on so return to main program flow
            returnToMainProgramLogic(resultsArray);
        }

    }
}

function returnToMainProgramLogic(results) {
    //continue running your program here
}

I personally like the above logic better than the syntax async produces... I believe the heart of your problem rests in your nested calls to async, and the fact that ASYN itself, runs the series of functions asynchronously, but in order(confusing I know). If you write your program like this, you won't have to worry about it!

MobA11y
  • 18,425
  • 3
  • 49
  • 76
  • Yeah I'm aware of these but I've been using async to get around them and it's been working fine until i used concat i guess. I will try to reorganize it and see if i can figure it out.. Thanks – Tolga E Jun 21 '13 at 19:03
  • This is great, thanks! I think i can follow the logic here, and it makes sense.. Much simpler the async hell i put myself into. – Tolga E Jun 21 '13 at 20:01
  • Yes, it is... did I mention my distaste for async? This is just so much cleaner! Also note: i moved the returnToMainProgramLogic function outside of the closure, this is marginally better! – MobA11y Jun 21 '13 at 20:03
  • yup that's great, thank.Also i'm guessing, after the definition of all the functions, you have to call databaseQuery1 to start the chain reactions right? – Tolga E Jun 21 '13 at 20:04
  • YES! :) Oops. Oh and I think the way it is right now you'll access one element past your dataRows array. I will modify. – MobA11y Jun 21 '13 at 20:05
  • Ok thanks, i can figure out the element stuff and so on.. I just needed the architecture of the calls. – Tolga E Jun 21 '13 at 20:06
  • there's one more issue, right now i'm in the 3rd query (one more than what you wrote) and here, I have to add an item to the resultsArray, but in order to do that, i need some data from the dbQuery2 and dbQuery1 to make the fully qualified json.. (like the name from the dbQuery1, the type from dbQuery2. How can i access these things from within the 3rd function? – Tolga E Jun 21 '13 at 21:07
  • Any variables you need access to within successive callbacks can be added to the scope of the closure. – MobA11y Jun 22 '13 at 03:35
  • Yeah i figured it out, thanks, it worked great and looks clean even though i have like 4-5 functions – Tolga E Jun 23 '13 at 21:13
0

I would strongly suggest using sequelize.js It provides a really powerful orm that allows you to chain queries together. It also allows you to directly load your data into js objects, write dynamic sql, and connect to many different databases. Picture ActiveRecord from the Ruby world for Node.

WallMobile
  • 1,939
  • 1
  • 20
  • 35