0

I need an array containing blobs, so my code is:

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

    var xhr = createXHR();

    xhr.open('GET', 'img/tiles/' + zeroFill(i, 4) + '.png', true);
    xhr.responseType = 'blob';

    xhr.onload = function() {

        arr[i] = new Blob([this.response], {type: 'image/png'});
        // console.log(arr[i]);

    };

    xhr.send();

}

When I output the i position of the arr, the console shows the blob correctly (at least, it says its size). If I try to display previous positions, I get undefined.

If I look the arr once all XHR requests have been completed, the console shows a weird array with every position undefined and the last with an uncompleted blob.

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
Scratz
  • 15
  • 2

2 Answers2

2

This a very common mistake. The onload handler gets called long AFTER your for loop has finished. That means that the value of i will be the value at the END of your loop, not what you want it to be from the middle of your for loop.

To fix it, you need to capture the correct value of i in a closure in some form. There are a number of ways to do it.

Here's one method using a self executing function that captures the value of i in a function argument. The value of i is passed to the self executing function which creates a new scope for each iteration of the for loop which then captures the correct value of i in the function argument in that scope. That function argument exists uniquely for each call to the self executing function so the desired value is preserved until it is needed when the onload handler is called at some time in the future. Here's how that would look:

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

    var xhr = createXHR();

    xhr.open('GET', 'img/tiles/' + zeroFill(i, 4) + '.png', true);
    xhr.responseType = 'blob';

    (function(index) {
        xhr.onload = function() {

            arr[index] = new Blob([this.response], {type: 'image/png'});
            // console.log(arr[index]);
       }

    })(i);

    xhr.send();

}
jfriend00
  • 683,504
  • 96
  • 985
  • 979
1

All of your ajax's callbacks reference the same i of the outer scope. That means when your ajax calls complete, they will all push the data to the same i which is total-1.

Side note: the previous indexes being filled with null is just how JS arrays work when you push data into a larger index.

A common solution is to use a closure, capturing the current i value into a new execution context:

//read comments in the numeric order
xhr.onload = (function(i) {//2. the `i` inside the function now references
                           //a new `i` independent from the outer `i`
    return function(){//3. returns a function handler that executes on xhr.onload
        arr[i] = new Blob([this.response], {type: 'image/png'});
        console.log(i); //4. logs the IIFE execution context's `i`,
                        //as it is the closest (inner-most scope chain-wise) `i`
    };
}(i)); //1. Passes outer current `i` as argument to this
       //Immediately-Invoked Function Expression (IIFE)

A more detailed explanation of the above code can be found here.

Fabrício Matté
  • 69,329
  • 26
  • 129
  • 166
  • You are absolutely right! So, yes: it is posible to create an array of blobs. I actually don't know why is the return function needed, but it works and I'm investigating about it. Thank you ;) – Scratz Jan 05 '13 at 02:01
  • I'll add some comments. There are hundreds of questions in SO that helps understanding it but it is a little complicated at first. – Fabrício Matté Jan 05 '13 at 02:02
  • @Scratz Added some comments and a link with more detailed explanation. Even though TJ's answer may be enough to get a basic view of what is happening, you're fated to repeat the mistake until you fully understand scope chain, so the more you read about it the better. `=]` The link I added in the answer should prove to be quite enlightening as well. – Fabrício Matté Jan 05 '13 at 02:20