1

I got myself into a tangle which probably involves multiple asynchronous callback situations.

I have a javascript function called populatePageArea()

Inside populatePageArea it traverses through this array-like variable called pages amongst other code.

function populatePagesArea() {
  // there was some code before the for loop

  for (var i=0, l=pages.length; i<l; i++) {    
    addToPagesArea(pages[i], "");   
  }

  // some code after...
}

Inside addToPagesArea function, I used the FileAPI from HTML 5 to preview the drag'n dropped files amongst other code.

function addToPages(file, front) {
  // there was some code before..
  reader = new FileReader();
  reader.onload = (function (theDiv) {
    return function (evt) {
      var backgroundimage = "url(" + evt.target.result + ")";
  theDiv.css("background-image", backgroundimage);
      var sizeSettings = getSizeSettingsFromPage(file, calculateRatio);

};

  }(imageDiv));

  // step#3 execute file reader
  reader.readAsDataURL(file);
  // there was some code after..
}

So every time I previewed the file, I also attempted to do some calculation on the dimensions of the file.

function getSizeSettingsFromPage(file, whenReady) {
    reader = new FileReader();
    reader.onload = function(evt) {
        var image = new Image();
        image.onload = function(evt) {
            var width = this.width;
            var height = this.height;
            var filename = file.name;
            if (whenReady) {
                whenReady(width, height, filename);
            }
        };
        image.src = evt.target.result; 
    };
    reader.readAsDataURL(file);

}
 function calculateRatio(width, height, filename) {

    var ratio = width/height;

    var object = new Object();
    object['height']    = width;
    object['width']     = height;
    object['ratio']     = ratio;
    object['size']      = 'Original';

    for (var size in SIZES) {
        var min = SIZES[size].ratio - 0.01;
        var max = SIZES[size].ratio + 0.01;

        if (ratio <= max && ratio >= min) {
            object['size'] = size;
        }
    }

    pageSizes.add(filename, object);

}

pageSizes as seen in calculateRatio is a global variable which is an array-like type of variable.

It is definitely empty BEFORE populatePagesArea gets called.

Here is the situation:

my code is:

populatePagesArea();
getMajorityPageSize(); // this acts on the supposedly non-empty pageSizes global variable

But because I think the calculateRatio has not been called on ALL the previewed images, the pageSizes is always empty when getMajorityPageSize is called.

how can i make sure that after populatePagesArea is called, getMajorityPageSize gets triggered ONLY after all the pages have undergone calculateRatio function?

I believe this is asynchronous callback. But I am not sure how to do it for an array of objects that will need to undergo an async callback function like calculateRatio.

Kim Stacks
  • 10,202
  • 35
  • 151
  • 282

1 Answers1

1

The simple solution (I have marked my changes with // ***:

// ***
var totalPages;

function populatePagesArea() {
  // there was some code before the for loop

  // ***
  totalPages = pages.length;
  for (var i=0, l=pages.length; i<l; i++) {    
    addToPagesArea(pages[i], "");   
  }

  // some code after...
}

function addToPages(file, front) {
    // there was some code before..
    reader = new FileReader();
    reader.onload = (function (theDiv) {
      return function (evt) {
        var backgroundimage = "url(" + evt.target.result + ")";
        theDiv.css("background-image", backgroundimage);
        var sizeSettings = getSizeSettingsFromPage(file, calculateRatio);
        // *** Check to see if we're done after every load
        checkPagesReady();
      };

    }(imageDiv));

    // step#3 execute file reader
    reader.readAsDataURL(file);
    // there was some code after..
}

// *** Call getMajorityPageSize() here, only after all pages have loaded.
function checkPagesReady() {
    if (pageSizes.length >= totalPages)
        getMajorityPageSize();
}

The better solution if you're going to be dealing with more asynchronous things later on would be to refactor your code using promises. Promises is an API designed for dealing with asynchronous programming in a systematic and organized way. It'll make your life a lot easier if you're going to be doing more async work. There's a lot of free libraries that support promises, one of the major players is Q.js.

Nathan Wall
  • 10,530
  • 4
  • 24
  • 47
  • Hi Nathan, thank you! i only made one change to your code. I moved the calling of checkpagesready to the last line of calculateRatio. – Kim Stacks Nov 27 '12 at 13:56
  • 1
    by the way, i came across this https://github.com/caolan/async#series compared with promises, which one is er.. more promising? Heh, pun intended. – Kim Stacks Nov 27 '12 at 13:57
  • If you're going to be working a lot with Node, the Async library you linked to may be pretty helpful because it follows Node conventions more closely. However, I think promises are a more robust and powerful solution, and there have been some efforts to move Node in that direction as well. In addition, there are a lot of discussions about [adding promises to a future version of ECMAScript](http://wiki.ecmascript.org/doku.php?id=strawman:concurrency). So I definitely think they are worth learning. – Nathan Wall Nov 27 '12 at 15:38
  • I saw the Pyramid of Doom described in Q.js README on github. I am already sold on it. I have several such Pyramids in my code right now! Thanks for the suggestion, Nathan! – Kim Stacks Nov 28 '12 at 00:42
  • Hi Nathan, I took your advice and tried to refactor using Promises. However, having read the documentation, i am still not too sure how to go about refactoring. I have distilled the most essential parts into a github repo and into this question. http://stackoverflow.com/questions/13597909/how-to-use-q-js-promises-to-work-with-multiple-asynchronous-operations Hope that you can give me some pointers. – Kim Stacks Nov 28 '12 at 04:38