2

I'm in deep trouble trying to understand how to make my code asynchronous in Node.js land. Please see my code below, in two varieties.

Ok so here's my first try - I have three functions here. A processing function (iim), a file copy function (fse.copy), and an archive function (da).

I need da to happen after iim, and iim to happen after fse.copy.

This first approach results in archive happening, but it's empty because iim never appears to happen.

  da(randomString, function(err) {
    if (err) {
      log.error(err);
    } else {
      fse.copy(temp_path, new_location + file_name, function(err) {
        if (err) {
          log.error(err);
        } else {
          log.info("File saved to " + new_location + file_name);
          var sourceImage = new_location + file_name;
          log.debug(sourceImage);
          log.debug(randomString);

          iim(sourceImage, randomString, function(err) {
            if (err) {
              log.error(err);
            }
          });
        }
      });
    }
  });

The next block is an alternate approach which results in the da happening before iim is finished.

  fse.copy(temp_path, new_location + file_name, function(err) {
    if (err) {
      log.error(err);
    } else {
      log.info("File saved to " + new_location + file_name);
      var sourceImage = new_location + file_name;
      log.debug(sourceImage);
      log.debug(randomString);

      iim(sourceImage, randomString, function(err) {
        if (err) {
          log.error(err);
        }
      });
      da(randomString, function(err) {
        if (err) {
          log.error(err);
        }
      });
    }
  });
4
  • you say that you want fse.copy ... iim ... da. Why do you call da first in the first example? Commented Jan 16, 2015 at 20:09
  • What does but it's empty mean? And please name your functions probably. Commented Jan 16, 2015 at 20:09
  • @akonsu, too much confusion and staring at this problem for too long, I think! Sorry. Do you have a suggestion with fse.copy ... iim ... da sequence please? Commented Jan 16, 2015 at 20:41
  • @Amberlamps, Sorry. da is an archive function, it zips a directory. This directory is populate by image processing (iim). In the example you speak of, the archive function happens before the image processing function can finish, resulting in an incomplete archive. I need this all to be asynchronous. Commented Jan 16, 2015 at 21:04

2 Answers 2

2

Here's what I'd recommend -- in your question you say you need to essentially run three functions in series -- correct? Run function A, then function B, and lastly, run function C.

The simplest way to do this is using the asyncjs library.

Here's an example:

var async = require('async');

async.series([
  function a(cb) {
    // do stuff
    cb();
  },
  function b(cb) {
    // do stuff
    cb();
  },
  function c(cb) {
    // do stuff
    cb();
  },
], function() {
  // this will run once all three functions above have finished
});

Now, let's say that each of those functions needs to return data to the next function. SO imagine that function B needs input from function A to run. How do you accomplish that? Using async.waterfall!

var async = require('async');

async.waterfall([
  function a(cb) {
    // do stuff
    cb(null, 'value');
  },
  function b(val, cb) {
    // do stuff with val
    cb(null, 'woot');
  },
  function c(val, cb) {
    // do stuff with val
    cb(null);
  },
], function() {
  // this will run once all three functions above have finished
});

Not bad right?

Hope this helps!

EDIT: Here's a code block showing your code above refactored using asyncjs:

async.waterfall([
  function(cb) {
    fse.copy(temp_path, new_location + file_name, function(err) {
      if (err) {
        log.error(err);
      } else {
        log.info("File saved to " + new_location + file_name);
        var sourceImage = new_location + file_name;
        log.debug(sourceImage);
        log.debug(randomString);
      }
      console.log('Finished running fs.copy');
      cb(null, sourceImage, randomString);
    });
  },
  function(sourceImage, randomString, cb) {
    iim(sourceImage, randomString, function(err) {
      if (err) {
        log.error(err);
      }
      console.log('Finished running iim');
      cb(null, randomString);
    });
  },
  function(randomString, cb) {
    da(randomString, function(err) {
      if (err) {
        log.error(err);
      }
      console.log('Finished running da');
      cb();
    });
  }
], function() {
  console.log('All done!');
});
Sign up to request clarification or add additional context in comments.

4 Comments

That's actually super helpful, thank you @rdegges. :-) I'm having difficulty equating this to my actual code since the functions aren't quite as straightforward. Would you be willing to help me with my actual code block please?
Hey @davidjpeacock. Just added a fully refactored code sample to the bottom =)
Good grief @rdegges! Thank you so very much. This kind of thing is amazing. You're wonderful. :-) I have further issues which must mean my modules aren't behaving. I'll edit the put output above, basically console.log('Finished running iim'); and console.log('Finished running da'); aren't being run; presumably this means those functions aren't working correctly.
Heyo! I just saw your edits (didn't approve it because it might be confusing to other SO readers), but yes. Those functions aren't working properly. What I'd do is this: open a new SO question about fixing those modules separately (include their code), then link to it from here in the comments =)
1

So you can either put da into the callback for iim (right now it's not) from your second example:

fse.copy(temp_path, new_location + file_name, function(err) {
    if (err) {
      log.error(err);
    } else {
      log.info("File saved to " + new_location + file_name);
      var sourceImage = new_location + file_name;
      log.debug(sourceImage);
      log.debug(randomString);

      iim(sourceImage, randomString, function(err) {
        if (err) {
          log.error(err);
          return;
        }
        da(randomString, function(err) {
          if (err) {
            log.error(err);
          }
        });

      });
    }
  });

That said, callback depth can be flattened with the use of a library like async (https://github.com/caolan/async)

3 Comments

Callback depth can also be flattened easily and naturally, by using named functions that are written down per module "flat" instead of ever deeper inline. Combine that with a modular approach and well-defined interfaces and JS has no "callback hell".
nobody should need a whole library to write non-nested async in a closure-ready environment...
Thank you @Paul, I have actually tried your suggestion but this results in da() not being run, with no errors. I'm further confused because this is how it 'should' work right?