0

I have this (piece of) my website, where I need to grab data from the TVDB/the MovieDB API. I need the screenshot of an episode, so I use

theMovieDb.tvEpisodes.getImages({
"id": showID,
"season_number": season_number,
"episode_number": episode_number
}, function(data){}, function(error){})

I need to execute it 6 times, because I need 6 images with 6 different episodes.

for(var i = 0; i < 7; i++){

}

But the problem is, if I now ask console.log(i) inside the function(data){} part, it logs 6, six times...
What did I do wrong?

This is the full code, with variables from other functions etc...

      for(var i = 0; i < 6; i++) {
    iF = i;
    u = i + 1
    epLen = data.episodes[data.episodes.length - 1 - i]
  var thies = "<center><span style='font-family: Arial'>" + epLen.season_number + 'x' + epLen.episode_number + ": <br>" + epLen.name + "<br>"+ epLen.air_date +"</span></center>";
  var thisId = "recent" + u.toString();
  var text = thies;
  document.getElementById(thisId).innerHTML = text;
  theMovieDb.tvEpisodes.getImages({"id": showID, "season_number": epLen.season_number, "episode_number": epLen.episode_number},
  function(data){
    var data = JSON.parse(data);
    window.still = data.stills[0].file_path;
    console.log(window.still);
    console.log(iF); // logs 5
    console.log(i); // logs 6
},
  function(error){})
  //alert(i)

  //var still = window.still;
  //console.log(still);
  //var epStil = "https://image.tmdb.org/t/p/original" + still.stills[0].file_path;
  //var epStill = "url(" + epStil + ")";
  //document.getElementsByClassName("recent")[i].style.backgroundImage = epStill;

}

EDIT
I found the solution, and since it is marked as a duplicate, I can't answer anymore, so I'll post it here:

epStilArray = [];
  for(var i = 0; i < 6; i++) {
    iF = i;
    u = i + 1
    epLen = data.episodes[data.episodes.length - 1 - i]
  var thies = "<center><span style='font-family: Arial'>" + epLen.season_number + 'x' + epLen.episode_number + ": <br>" + epLen.name + "<br>"+ epLen.air_date +"</span></center>";
  var thisId = "recent" + u.toString();
  var text = thies;
  document.getElementById(thisId).innerHTML = text;
  theMovieDb.tvEpisodes.getImages({"id": showID, "season_number": epLen.season_number, "episode_number": epLen.episode_number},
  function(data){
    var data = JSON.parse(data);
    still = data.stills[0].file_path;
    var epStil = "https://image.tmdb.org/t/p/original" + still;
    var epStil = "url(" + epStil + ")";
    epStilArray.push(epStil);
    console.log(epStilArray);
    document.getElementsByClassName("recent")[epStilArray.length - 1].style.backgroundImage = epStil;
},
  function(error){})
}

3 Answers3

1

You have a closure problem. There are numerous resources to learn more about closures in js. Here is the JSLint entry on it specific to loops.

You can fix it by creating an IIFE and passing in a copy of your counter.

for(var i = 0; i < 6; i++) {
    (function (iCopy) {
        iF = iCopy;
        u = iCopy + 1
        epLen = data.episodes[data.episodes.length - 1 - iCopy]
        var thies = "<center><span style='font-family: Arial'>" + epLen.season_number + 'x' + epLen.episode_number + ": <br>" + epLen.name + "<br>"+ epLen.air_date +"</span></center>";
        var thisId = "recent" + u.toString();
        var text = thies;
        document.getElementById(thisId).innerHTML = text;
        theMovieDb.tvEpisodes.getImages({"id": showID, "season_number": epLen.season_number, "episode_number": epLen.episode_number},
            function(data){
                var data = JSON.parse(data);
                window.still = data.stills[0].file_path;
                console.log(window.still);
                console.log(iF); // logs 5
                console.log(iCopy); // logs 6
            },
            function(error){})
    }(i));
}
trenthaynes
  • 1,668
  • 2
  • 16
  • 28
  • I tried it, and here's my log: `[Log] /tNtdHIhLwQurJ4430n7scKNjPCg.jpg (index.html, line 288) [Log] 5 (index.html, line 289) [Log] 6 (index.html, line 290) [Log] /z2E4Rq7D5TmAdzS9D6Wije3sGI3.jpg (index.html, line 288) [Log] 5 (index.html, line 289) [Log] 6 (index.html, line 290) [Log] /3cEVM4peZW2r2HiKVzxDjmBLJxD.jpg (index.html, line 288) //etc` so still only 5 & 6... – R. from the North May 15 '15 at 08:16
1

This is a great example of a spot where you can use a JavaScript closure to ensure each instance of the inline function has access to a different value for i.

One way to do that would be to wrap the code up in an immediately-invoked function expression (IIFE), and capture a copy of the desired variable inside a different variable tied to the scope of that function expression. This could be done in the format of (function(){ var iCopy = i; /*your code here*/})(); or (function(iCopy){ /*your code here*/})(i);

Setting the copied variable in its own line within the IIFE:

for(var i = 0; i < 6; i++) {
    ...
    (function(){
        var iCopy = i; // i is copied to iCopy
        theMovieDb.tvEpisodes.getImages({},
            function(data){
                ...
                console.log(iCopy); 
            }, function(error){});
    })(); // this IIFE has its own scope capturing a copy of the variable i
 }

alternatively, passing the variable as a parameter to the IIFE:

for(var i = 0; i < 6; i++) {
    ...
    (function(iCopy){ // i is copied to iCopy
        theMovieDb.tvEpisodes.getImages({},
            function(data){
                ...
                console.log(iCopy); 
            }, function(error){});
    })(i); // this IIFE has its own scope capturing a copy of the variable i as a parameter
}

Also note that you don't have to change the copied variable's name; I only did that in the above example for the sake of clarity. If two variables in different scopes share the same name, the code will reference the more immediate (innermost) scope. Hence, the following code would also work:

for(var i = 0; i < 6; i++) {
    ...
    (function(i){ // i is copied to i
        theMovieDb.tvEpisodes.getImages({},
            function(data){
                ...
                console.log(i); 
            }, function(error){});
    })(i); // this IIFE has its own scope capturing a copy of the variable i as a parameter
}
Thriggle
  • 7,009
  • 2
  • 26
  • 37
0

In order to isolate the problem I've just put the functional call inside of the loop and it worked fine. The issue indeed could be related to asynchronous workflow and closures, so if for you the order matters, just pass the variable "i" in the closure.

For example, next code logs: i = 0. i = 1. i = 2. i = 3. i = 4. i = 5

function myFunction(f) {f()}

for (var i = 0; i < 6; i++) {
    myFunction(function() { console.log('i = ' + i); })
}

Also in your question I've seen that you use "var i" and "iF" w/o "var" work in different scopes.

Axalix
  • 2,831
  • 1
  • 20
  • 37
  • What is your specific question/problem? What are you trying to do? What is the expected output of the function? Please edit your post and elaborate. – Brino May 14 '15 at 15:40