0

I was cleaning up my code and ran into a little trouble with callbacks, specifically getting the correct values to output when a callback is called. Could some explain to me why the following code spits out something that I'm not expecting and a possible solution without having to put in another parameter of i to the run() function, or is passing in i to know my index upon calling the callback the only way to do this?

for (var i in dls) {
    run(dls[i][0], dls[i][1], function(isTrue){
        if (isTrue) {
            // Do true stuff here
        } else {
            console.log("Value is: " + dls[i][3])
        }
    });
}

Calling run() actually has the correct inputs inside, but upon that function calling the callback and going into the else statement, dls[i][3] spits out the same value i times.

I've tried putting different scopes around (run()) and such but to no avail and can't seem to wrap my head around this.

Thanks

EDIT:

If I wanted to split it up into a separate function, how would I do it?

var run = function(cb){
    setTimeout(function() {
        cb(false)
    }, 3000);
}

for (var i in dls) {
    run(dls[i][0], dls[i][1], (function(index) {
        return extraction
    })(i));
}

function extraction(isTrue){
    if (isTrue) {
        // stuff
    } else {
        console.log("Nothing changed in " + dls[i][3])
    }
}

Here dls[i][3] is still incorrect and prints the same value 3 times.

Bryan
  • 1,438
  • 3
  • 15
  • 24
  • 1
    Possible duplicate of [understanding the concept of javascript callbacks with node.js, especially in loops](http://stackoverflow.com/questions/4506240/understanding-the-concept-of-javascript-callbacks-with-node-js-especially-in-lo) – Tuvia Apr 18 '16 at 20:04
  • I've looked at that thread and still couldn't find a potential solution to it, hence why I asked, but thanks for pointing me in the right direction. – Bryan Apr 18 '16 at 20:14

2 Answers2

2

You have fallen into the traditional "loop trap"

When it comes time for your callback to run i is now a different value.

What you can do is cache that value in another wrapper function:

for (var i in dls) {
    run(dls[i][0], dls[i][1], (function (currentIndex) { 
        return function(isTrue){
            if (isTrue) {
                // Do true stuff here
            } else {
                console.log("Value is: " + dls[currentIndex][3])
            }
        };
    })(i));
}
Tuvia
  • 859
  • 4
  • 15
  • Thank you for the response! Okay, a follow up to this. Let's say I don't want to create a new function every iteration. function extraction(isTrue){ console.log(dls[i][3]) } for (var i in dls) { run(dls[i][0], dls[i][1], (function(index) { return extraction })(i)); } Why doesn't this work? – Bryan Apr 18 '16 at 20:15
  • Because your callbacks happen at a later time when `i` is already set as a different value. – Tuvia Apr 18 '16 at 20:18
  • Hmm, how would I pass the cached value into the function without having to pass it in via the callback? Or would I have to now? – Bryan Apr 18 '16 at 20:21
  • @Bryan you can just do as what is in my answer :-) – Tuvia Apr 18 '16 at 20:29
  • I'll play around with it since I sort of have an understanding. Just one last question, what does the (i) at the end of the run(.....)(i) function do? – Bryan Apr 18 '16 at 20:32
  • I was trying to figure out what to do if I wanted to separate the function(isTrue){....}; and make that an external function. I don't seem to be in the scope for currentIndex anymore if I were to make the function outside of the for loop and seem to still run into the same repeated value problem. – Bryan Apr 18 '16 at 20:34
  • 1
    The `i` at the end of the function is actually an argument inside of the function invocation. Since the value of `i` is passed to the function, instead of just referencing the original `i` inside the internal callback, it will be the value as you expect. You could also look into using `let` in ES6, which would solve the issue without requiring an IIFE. More reading [here](http://benalman.com/news/2010/11/immediately-invoked-function-expression/). – dvlsg Apr 18 '16 at 20:55
  • 1
    Very helpful and useful read, thanks @dvlsg, learned something new today! – Bryan Apr 18 '16 at 22:00
  • @dvlsg Do you think you can help me on the edited question above? – Bryan Apr 18 '16 at 22:20
  • Sure - there are actually a couple of issues. `extraction` is never passed the correctly scoped `i`. `run` also is being called with 3 arguments, but the function only accepts one `cb` argument. I'll paste an answer in regards to the second question. – dvlsg Apr 18 '16 at 22:53
  • Actually, I may need more information to answer that second question properly, come to think of it. There are some errors as well -- for example, `extraction` tries to use the variable `i`, which is *sort of* in scope, but I think it's accidentally in scope. What you'll want to do is make a wrapper function around `extraction`. The wrapper should accept on argument `i`, then return the `extraction` function (just a reference to it, don't actually execute it) which makes use of the `i` from the argument, not the global scope. – dvlsg Apr 18 '16 at 22:56
1

In regards to the edit / second question, assuming this is what you wanted to do:

// note that I changed the function signature of `run`
var run = function(val1, val2, cb) {
    setTimeout(function() {
        cb(false);
    }, 3000);
};

// note the `wrapper` here
for (var i in dls) {
    run(dls[i][0], dls[i][1], wrapper(i));
}

// this is the same as what the IIFE is doing,
// just with an external function instead
function wrapper(scopedIndex) {

    // return a function to be used as the callback for `run`
    return function extraction(isTrue) {
        if (isTrue) {
            // stuff
        }
        else {
            // use the scoped index here
            console.log("Nothing changed in " + dls[scopedIndex][3]);
        }
    }
}

Take a look at function makeExitCallback(i) in the other linked question, as well. It directly relates to what's going on here.

You should post what's in dls as well, just to make it easier to run your snippets locally.

Community
  • 1
  • 1
dvlsg
  • 5,378
  • 2
  • 29
  • 34
  • I wrote something similar to this that worked! I found that the reason why I'm had so much trouble was because I didn't really and truly understand the concept of IIFE. I read this http://gregfranko.com/blog/i-love-my-iife/ and it was pretty helpful telling me what each component was. Thanks dvlsg for the pointer. :-) – Bryan Apr 18 '16 at 23:05
  • Yup, IIFE's always seem to take newer JS devs by surprise. They sure confused me the first time I saw one. Glad you got it sorted out. – dvlsg Apr 18 '16 at 23:06
  • Thanks again. A little bit of practice and I might be able to teach someone else, haha. I wasn't sure exactly what to search regarding this question but looks like 6 hours and a helpful guide was all I needed to find about IIFE. – Bryan Apr 18 '16 at 23:11