1

I know that objects are copied by reference in javascript, but this is strange.

var project = projects[projectIndex];

var projectName = project[0]["repository"]["name"];
console.log("type" + " of name " + projectName + " in " + ownerFolderName);

projectTasks.push(function(callback){
    omnifocus.create_folder_if_possible_in_group(projectName, ownerFolderName, function () {
      callback();
    });
});

In this snippet is run multiple times in a loop. The projectName is correct when logged with console.log. However, when used in the anonymous function (in projectTasks), the value is always the same value as the last item.

For example : if it's looped three times, it will log "1", "2", "3" (which is correct). However, in the anonymous function, it will run three times with the "3" value.

Full code is available here : https://github.com/gcamp/github-omnifocus-sync/blob/master/index.js

gcamp
  • 14,622
  • 4
  • 54
  • 85
  • 2
    Because it is an asynchronous operation and your projectName is already "3" by the time it enters your anonymous function. –  Sep 04 '14 at 03:25

2 Answers2

3

This is the standard closure problem.

You have a variable called projectName. You're defining functions in a loop that access this variable. But these functions are not using the value of the variable at the point of definition - they will use the value of the variable at the point of invocation. Because you're looping through the projects and changing projectName in the process, by the time the function is invoked, the value is the last value in the loop.

The standard way to solve this is to use an IIFE to restrict the variable on a per-loop basis:

for (var projectIndex in projects) {
    var project = projects[projectIndex];

    var projectName = project[0]["repository"]["name"];
    console.log("type" + " of name " + projectName + " in " + ownerFolderName);

    (function(projectName) {
        projectTasks.push(function (callback) {
            omnifocus.create_folder_if_possible_in_group(projectName, ownerFolderName, function () {
                callback();
            });
        });
    })(projectName);
}
sahbeewah
  • 2,690
  • 1
  • 12
  • 18
1

"New User" correctly pointed out that your push function is probably asynchronous, so you've completed your for loop before the callback function is invoked.

Remember that unlike other C/Java-related languages, variables in javascript are scoped by function rather than by curly-braces {}. So to ensure that your closed-over variable is not changed as you go through your loop, you'll need to pass its value into a function. There are many ways to go about this, but here's one that doesn't require much change to your code:

function makeCallback(projectName)
{
    return function(callback){
        omnifocus.create_folder_if_possible_in_group(
            projectName, ownerFolderName, 
            function () {
                callback();
            });
    };
}

...

var project = projects[projectIndex];

var projectName = project[0]["repository"]["name"];
console.log("type" + " of name " + projectName + " in " + ownerFolderName);

projectTasks.push(makeCallback(projectName));

Another alternative could be to use Array.prototype.forEach() instead of your for loop.

StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315