Skip to content

Instantly share code, notes, and snippets.

@shiffman
Last active September 28, 2015 17:41
Show Gist options
  • Save shiffman/7b96d9951bb3865e7d38 to your computer and use it in GitHub Desktop.
Save shiffman/7b96d9951bb3865e7d38 to your computer and use it in GitHub Desktop.
Discussion of issues around callbacks for multiple elements in a loop. Using setTimeout() here for demonstration purposes. Actual scenario relates to examples here: https://github.com/shiffman/Programming-from-A-to-Z-F14/tree/master/week6_apis
// While this seems like the right idea
// This does not work at all!
// The loop will finish and i will be at 10 by
// the time the anonymous function is called
for (var i = 0; i < 100; i++) {
setTimeout(function() {
console.log(i);
}, 100*i);
}
// Thanks to Sam Lavigne for suggesting this nice-looking methodology
for (var i = 0; i < 10; i++) {
// This will work by passing in a variable
// to a separate function
countit(i);
}
function countit(num) {
// This is a closure where
// the function declared here will retain
// num even after this has been completed
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures
setTimeout(function() {
console.log(s + num);
}, num * 1000);
}
// Option #2
// Function factory
function count(val) {
return function() {
console.log(val);
}
}
for(var i = 0; i < 100; i++) {
setTimeout(count(i), i*100);
}
// Option #3
// This is basically the same thing but using bind()
function count(val) {
console.log(val);
}
for(var i = 0; i < 100; i++) {
setTimeout(count.bind(null, i), i*100);
}
// Option #4
// Same as #2, but anonymous self-executing
function count(val) {
console.log(val);
}
for(var i = 0; i < 100; i++) {
setTimeout(function(val) {
return function() {
console.log(val);
}
}(i), i*100);
}
// Thanks to Sam Lavigne for suggesting this nice-looking methodology
for (var i = 0; i < 10; i++) {
// This will work by passing in a variable
// to a separate function
countit(i);
}
function countit(num) {
// This is a closure where
// the function declared here will retain
// num even after this has been completed
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures
setTimeout(count, num * 1000);
function count() {
console.log(num);
}
}
@desandro
Copy link

Option #2. As it turns out, you are not 'creating a new function inside the loop.' You are doing the opposite, by using a function that returns a function. All done outside the loop. This is a good thing!

Related - are you using JSHint? It would have caught that #1 and #4 are bad.

@shiffman
Copy link
Author

@antiboredom wow, i'm not sure how I missed thinking of it that way! @desandro @NV thanks for the thoughts!

@shiffman
Copy link
Author

@benchun
Copy link

benchun commented Oct 17, 2014

I agree with @desandro that you want the to name and define the function outside the loop to avoid creating multiple copies of the function (as #4 would do). @antiboredom your version is easy on the eyes, and makes it clear that we want to form a closure over i each time count is called in the loop.

In ES6 there will be a let keyword, allowing you to express this idea even more elegantly:

for(var i = 0; i < 100; i++) {
  let j = i;
  setTimeout(function(){
    console.log(j);
  }, i*100);
}

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let

@jkosoy
Copy link

jkosoy commented Oct 17, 2014

I don't see anything wrong with any of these. JS is like the jazz of the programming world. :)

Option #2 is actually a textbook example of a closure and is great for private functions. That's a proper engineering interview question. :) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures

@Winchestro
Copy link

var t= new Array(100).map(function(e,i){
    return setTimeout(function(){
        console.log(i);
        //at this point we can access both the timeout index e (for clearing) and the array index i
        //also the array of all timeouts t
    },100);
});

//something went wrong, we must cancel them
t.forEach(function(e,i){
    clearTimeout(e);
});

For other async code that's not a timeout the array would be an array of promises you can pass to Promise.all() for example.

The most elegant solution I can think of is using 'let', but that's already been mentioned by ben. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment