Async function nested within async.js waterfall
Asked Answered
S

2

5

Disclaimer: non-engineer, very new to JS

Hey all - I'm trying to leverage the async.js module to chain a group of functions together. My desired output is to iterate over mapData (array of objects), prior to passing it to the final function (right now - just console.log( result ).

async.waterfall([
    function( callback ) {
        getCoords ( function( data ) {
            mapData = data;
        });
        callback(null, mapData);
    },
    function( mapData, callback ) {
        //getEmail ( mapData );
        callback( null, mapData );
    } 
    ], function( err, result ) {
    console.log( result );
});

However, getCoords contains another async function (found here). What I'm seeing is that the first callback ( null, mapData) happens before it returns, leading to a null result.

How do I structure this so that the getCoords returns mapData prior to proceeding to the next block? I am probably missing something super obvious, thanks!

Schott answered 29/3, 2014 at 16:19 Comment(3)
Is this all you're doing, and if so, why do you even need async, just place the getEmail function inside the getCoords callback ?Buryat
@Buryat this might just be a small part of a larger implementation for which philsometypaway needs something to implement continuous passing style.Xuthus
@JasonAller is correct - i am going to need to chain a bunch of these together. thanks for your help guys!Schott
B
8

Callback fun... You need to understand how program flow works when using callbacks. This can bee seen with a very simple example.

Example:

function doWork( callback ) {
  console.log( 2 );
  setTimeout(callback, 1000);
}

function master () {
  console.log(1);

  doWork(function () {
    console.log(3);
  });

  console.log(4);
}
master();

The expected results would be console logs in the propper order 1, 2, 3, 4. But when running the example you see something strange as the logs are out of order 1, 2, 4, 3. This is because logging of 3 happens after doWork finishes, while logging of 4 happens after starting doWork, not waiting for it to finish.

async:

You can do a lot with the async library, but the most important thing to remember is that the callback function is always to receive the error as the first argument followed by the arguments you want to pass to the next function in the list.

The gist you linked to is not setup to return that way. You can either fix it or deal with it in your code. First lets just use the function as is:

Use existing getCoords:

async.waterfall([
  function( callback ) {
    // getCoords only returns one argument
    getCoords ( function( mapData ) {
      // First argument is null because there
      // is no error. When calling the waterfall
      // callback it *must* happen inside the getCoords
      // callback. If not thing will not work as you
      // have seen.
      callback( null, mapData);
    });
  },
  function( mapData, callback ) {
    // Do work with the results of the 1st step
    // in the waterfall.

    // Finish the water fall
    callback( null, mapData );
  } 
], function( err, result ) {
  if ( err ) {
    console.log( err );
    return;
  }
  console.log( result );
});

Now there are two issues with getCoords. First is that it does not return the correct arguments to it's callback, second it doesn't always use its callback. This second issue is huge as it will cause your program to hang and break.

I've commented in the function the 2 fixes that were made.

Fixed getCoords:

function getCoords ( callback ) {   
  var query = new Parse.Query( 'userGeoCoordinates' );
  query.exists( 'location' )
  query.find( {
    success: function ( result ) {
      for ( var i = 0; i < result.length; i++ ) {
        var object = result[ i ];
        var user = {};

        user.userId = object.get( 'userId' );
        user.coords = [];

        if ( !user_dedupe( user.userId ) ) {
                all_users.push( user );
        }
      }

      for ( var i = 0; i < all_users.length; i++ ) {
        for ( var j = 0; j < result.length; j++ ) {
          var object = result [ j ];
          if( object.get( 'userId' ) == all_users[ i ].userId ) {
            all_users[i].coords.push(
              [ object.get( 'location' )._longitude , object.get( 'location' )._latitude ]
            );
          }
        }

      }
      // This is the original callback, let fix it
      // so that it uses the normal return arguments
      // callback( all_users );

      // Return null for no error, then the resutls
      callback( null, all_users );
    },
    error: function( error ) {
      // Here is the second, bigger, issue. If the
      // getCoords had an error, nothing the callback
      // isn't called. Lets fix this
      //  console.log( 'error' );

      // Return the error, an no results.
      callback( error );
    }
  });
}

With the getCoords function fixed you can simplify your waterfall:

1st Simplified Waterfall:

async.waterfall([
  function( callback ) {
    // getCoords returns the expected results
    // so just pass in our callback
    getCoords ( callback );
  },
  function( mapData, callback ) {
    // Do work with the results of the 1st step
    // in the waterfall.

    // Finish the water fall
    callback( null, mapData );
  } 
], function( err, result ) {
  if ( err ) {
    console.log( err );
    return;
  }
  console.log( result );
});

But async has a nice feature. If your waterfall step is simply calling a function that returns the expect results you can simplify it further by using async.apply.

2st Simplified Waterfall:

async.waterfall([
  async.apply( getCoords ),
  function( mapData, callback ) {
    // Do work with the results of the 1st step
    // in the waterfall.

    // Finish the water fall
    callback( null, mapData );
  } 
], function( err, result ) {
  if ( err ) {
    console.log( err );
    return;
  }
  console.log( result );
});
Bypath answered 29/3, 2014 at 18:25 Comment(2)
Hi, I am new one in javascript and i want to ask a question that in your example you called callback( null, mapData ); or say everyone uses callback() at end where i see async.waterfall. I want to know what is use of callback function and where it is defineBridegroom
@Bridegroom - the callback function is defined for you as part of the waterfall. It is passed as the last argument to each function in in the array. When you call this function it basically is calling the next function in the array.Bypath
C
2

Try putting the callback for the first function of the waterfall inside of the getCoords callback. This way the second function in the waterfall will be called only after getCoords callback has set the mapData, additionally mapData will now be in scope when you invoke the callback.

async.waterfall([
    function( callback ) {              // F1
        getCoords ( function( data ) {
            mapData = data;
            callback(null, mapData);    // this is the entry point for F2
        });
    },
    function( mapData, callback ) {     // now F2 is invoked only after mapData is set
        //getEmail ( mapData );
        callback( null, mapData );
    }], 
    function( err, result ) {
        console.log( result );
    });
Coan answered 29/3, 2014 at 18:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.