2

i have xhr with for loop and it work very rare

for(var i = 0; i < this.files.length; i++) {
    var xhr = new XMLHttpRequest();

    xhr.upload.onprogress = function(e) {

    };

    xhr.onreadystatechange = function(e) {
        if(this.readyState === 4) {
            console.log(xhr.responseText);
        }
    };
    var formdata = new FormData();
    formdata.append("files", this.files[i]);
    console.log(this.files[i]);
    xhr.open('POST', 'slike.php');
    xhr.send(formdata);
}

I'm calling this slike.php. And it work very well, but on responseText, it's not good, sometimes get only last file from loop, sometimes get two file(with same text). I don't know how ti fix it, i was looking everywhere, and can't found the answer.

Nebojsa Sapic
  • 89
  • 1
  • 2
  • 7
  • possible duplicate of [How to handle Simultaneous javascript xmlhttprequests?](http://stackoverflow.com/questions/13445809/how-to-handle-simultaneous-javascript-xmlhttprequests) – Damien Sep 29 '14 at 12:53
  • @Damien duplicate of the duplicate haha. It seems that laruiss copied the answer without fixing details to this specific question. – DontVoteMeDown Sep 29 '14 at 13:00

2 Answers2

2

XHR are asynchronous by default, so unless you specify otherwise (async = false in XHR open() method), your loop may have finished before the first XHR may have been initialized.

But the i in your code (this.files[i]) in the loop refers to the same i of your loop, so i may be assigned this.files.length-1 when the first XHR begins. Taht is why you always get only the last file.

That is why you have to create what is called a closure to make sure the index you are using is the one you really want to use.

Try this :

for (var i = 0; i < this.files.length; i++) {
    (function(index, files) { // In this closure : parameters of a function in JS 
                              // are available only in the function,
                              // and cannot be changed from outside of it
        var xhr = new XMLHttpRequest(); // variables declared in a function in JS
                                        // are available only inside the function
                                        // and cannot be changed from outside of it

        xhr.upload.onprogress = function (e) {

        };

        xhr.onreadystatechange = function (e) {
            if (this.readyState === 4) {
                console.log(xhr.responseText);
            }
        };
        var formdata = new FormData();
        formdata.append("files", files[index]); // `index` has nothing to do with `i`, now:
                                                // if `i` changes outside of the function,
                                                //`index` will not
        console.log(files[index]); // Don't keep `console.log()` in production code ;-)
        xhr.open('POST', 'slike.php');
        xhr.send(formdata);
    })(i, this.files)
}

Or if would want to really get the files sequentially :

var i = 0,
    fileNb = this.files.length;

function getNextFile(file) {
    var xhr = new XMLHttpRequest();

    xhr.upload.onprogress = function (e) {

    };

    xhr.onreadystatechange = function (e) {
        if (this.readyState === 4) {
            console.log(xhr.responseText);
            if (++i < fileNb) getNextFile(this.files[i]);
        }
    };
    var formdata = new FormData();
    formdata.append("files", file);
    console.log(file); // Don't keep `console.log()` in production code ;-)
    xhr.open('POST', 'slike.php');
    xhr.send(formdata);
}
getNextFile(i);
laruiss
  • 3,780
  • 1
  • 18
  • 29
  • thanks for the answer, i'm sending files, with this code, php returns me error, and it says filename is empty – Nebojsa Sapic Sep 29 '14 at 12:56
  • This happens because the `this.files` have a different value inside the *closure*. You have to pass it like `(i, this.files)` then get it as parameter `function(index, files)`. – DontVoteMeDown Sep 29 '14 at 12:57
  • @laruiss it works now!, but there is a little problem, after pass all, order is not same, it is not a big problem, but why that happens? – Nebojsa Sapic Sep 29 '14 at 13:04
  • Because XHR are not synchronous by default. To help you understand, here is a simple example : if you begin downloading a big file before a small one, for example, you may receive the big file after the small one, event though you "asked" for it first. – laruiss Sep 29 '14 at 13:08
  • @laruiss thanks for explanation, just one more thing, why function inside loop, what that function change? And another user tell me to just put "this" instead "xhr" in readyState, what is different between yours solution and that? – Nebojsa Sapic Sep 29 '14 at 13:19
  • Well, it is a bit tricky to answer to this in a comment, but if I may, I recommend you to read about JavaScript scopes, iife, and closure to understand my solution... Or maybe I can edit it To explain. – laruiss Sep 29 '14 at 13:23
  • @laruiss sorry for disturbing, all files are sending with xhr in same time, is there any method to send them one by one, when first was finished, than starts second, I hope you understand me? – Nebojsa Sapic Sep 29 '14 at 14:10
  • Yes you can, you can either do synchronous XHR (although I would not recommend it), or modify the code to get the next file once the previous one has been received, in the xhr callback function. – laruiss Sep 29 '14 at 14:38
  • @laruiss How can I do it? I will appreciate answer, I try this with recursion but I can't do this. I am uploading pictures, and want progress bar for each image – Nebojsa Sapic Sep 29 '14 at 14:41
  • @laruiss thanks for editing your answer, you append("files", file) but file == i and number, should file be this.files or something like that, with this code, php file give me error empty file name, and i think there had to be something like i++ or what? – Nebojsa Sapic Sep 29 '14 at 14:51
2
console.log(xhr.responseText);

You are accessing the current value of xhr (which will usually be the last one created) there instead of the object to which the event handler is attached.

Use this instead of xhr as you did in the previous line.

Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335