sn0r
sn0r

Reputation: 544

nodejs, mysql, async itterative functions

I'm having a bit of trouble with an itterative function in nodejs.

I'm stepping through an object and checking if that object has any sub-objects attached (think: a star has a planet has a moon has an orbital station has a ship).

I'm trying to assemble this all into a nice array of objects to push to the client.

Here's the function:

        var subNodeProc = function(nodeList,sqlP,itteration_count) {
            var async = require('async');

            --itteration_count;
            async.each(nodeList,function(dd,cb){

                    var simple = {
                        sql:sqlP,
                        values:[dd.node_id],
                        timeout:40000
                    };

                    dd.subnodes = false;
                    connection.query(simple, function(err,rslt){
                        if (err) {
                            cb(err);
                        } else {
                            if (rslt.length > 0) {

                                var r = nodeList.indexOf(dd);
                                if (itteration_count > 0) {
                                    rslt = subNodeProc(rslt,sqlP,itteration_count);
                                }
                                nodeList[r].subnodes = rslt;

                            }
                            cb();
                        }
                    });

            },function(err){

                if (err) {
                    return err;
                } else {
                    return nodeList;
                }

            });

        }

When I trigger the function it returns a nodelist of undefined. Can anyone give me a pointer in the right direction? I can't get it to work

Thanks!

Edit: here's a sample of the data I'm itterating over:

The SQL statement:

SELECT n.id as node_id, n.name, n.system_id, n.parent_id as parent_id FROM nodes as n WHERE n.parent_id = ?

Sample nodeList for input:

[ { node_id: 1,
    name: 'Planet A',
    system_id: 1,
    parent_id: null,
},
{ node_id: 2,
    name: 'Moon',
    system_id: 1,
    parent_id: 1,
},
{ node_id: 3,
    name: 'Debris',
    system_id: 1,
    parent_id: 2,
},
{ node_id: 4,
    name: 'Asteroid',
    system_id: 1,
    parent_id: 1,
} ]

Moon A has a parent_id of 1 and node_id of 2, moon A also has a ship (ship A, node_id:3, parent_id:2) orbiting it.

What I want :

[ { node_id: 1,
    name: 'Planet A',
    system_id: 1,
    parent_id: null,
    subnodes:[{
        node_id: 2,
        name: 'Moon A',
        system_id: 1,
        parent_id: 1,
        subnodes: [{
            node_id:3,
            name: 'Ship A',
            system_id:1,
            parent_id:2
        },
        {...}]
    },
    {...}]
},
{...}]

Upvotes: 0

Views: 83

Answers (2)

sn0r
sn0r

Reputation: 544

Okay, the solution was pretty obvious, once I sussed it out. Much thanks to @shennan for getting me going.

The key is:

  1. as @shennan mentioned, you don't work with returns, as we're working asynchronously. This means callbacks

and

  1. You have to trigger callbacks for each part of the function. This is not possible with just one function, so to get the objects returning you need two, each doing different parts of the original function.

Here's what I've come up with. Hope someone can look it over and give me an opinion...

        // Main processing function. 
        var subNodeProc = function(nodeList,sqlP,itteration_count,cback) {
            var async = require('async');

            itteration_count--;
            async.each(nodeList,function(dd,cb){

                if (itteration_count > 0) {

                    // Trigger SQL Walker subNodeProcWalker with the necessary data (dd.node_id in this case, with a callback)
                    subNodeProcWalker(dd.node_id,sqlP,itteration_count,function(nl) {

                        // Hey look! the walker has done its business. Time to fill the subnode and tell async we're done with this array node.
                        dd.subnodes = nl;
                        cb();

                    });

                }

            },function(){

                // At the end of the run, return the nodelist intact.
                cback(nodeList);

            });

        }


        // SQL Walker with callback for subNodeProc
        var subNodeProcWalker = function(node_id,sqlP,itteration_count,cback){

            // assemble the object for the query and do the query
            var simple = {
                sql:sqlP,
                values:[node_id],
                timeout:40000
            };
            connection.query(simple, function(err,rslt){
                if (err) {

                    console.log('Error in Query');
                    console.log(simple);
                    console.log(err);
                    cback(false);

                } else {

                    // no error and a result? Quick! Trigger subNodeProc again
                    if (rslt.length > 0) {

                        subNodeProc(rslt,sqlP,itteration_count,function(nodePol) {

                            // Lookie lookie! A result from subNodeProc! There's life there! Quick! tell this function we're done!
                            cback(nodePol);
                        });

                    } else {

                        cback(false);

                    }

                }

            });
        }

Upvotes: 0

shennan
shennan

Reputation: 11656

It's hard to tell whether there are any other major issues because I cannot see the data which you are feeding the method. However, there is one major problem with this: you're attempting to return data from a method that uses asynchronous method calls.

The asynchronous way is to return values via a callback. In your code, the very last function in your example (the callback) is called from a completely different scope (from within the async framework) so your nodeList or err is being lost in a scope you don't control.

You need to rethink your code so that the returned data is passed to a callback. You could leverage the async callback for this. Ad a callback argument to your subNodeProc method. Then you can call that callback, after async has finished, passing it the nodeList:

var subNodeProc = function (nodeList, sqlP, itteration_count, cb) {

    var async = require('async');

    --itteration_count;
    async.each(nodeList,function(dd, cb){

      var simple = {
          sql:sqlP,
          values:[dd.node_id],
          timeout:40000
      };

      dd.subnodes = false;
      connection.query(simple, function(err, rslt){
          if (err) {
              cb(err);
          } else {
              if (rslt.length > 0) {

                  var r = nodeList.indexOf(dd);
                  if (itteration_count > 0) {
                      rslt = subNodeProc(rslt,sqlP,itteration_count);
                  }
                  nodeList[r].subnodes = rslt;

              }
              cb();
          }
      });

    }, function (err) {

        if (err)
            throw err;
        else
            cb(nodeList);

    });
}

You would then use the method like this:

subNodeProc(nodeList, sqlP, itteration_count, function (processed) {

  console.log(processed);

  /* do whatever you want afterwards here */

});

Upvotes: 1

Related Questions