0
  1. Listen to a "Keyup" in an input field
  2. Define "enableSubmitButton" as an array
  3. Do some stuff
  4. Add an array element to the "enableSubmitButton" array
  5. From the current input field I m searching the parent form, and then I loop all input fields
  6. In this loop, I make a request to the Server
  7. In the "onreadystatechange" function I push another element into the "enableSubmitButton" array

The problem is, that the element that I have pushed within the "onreadystatechange" is not really in the array. When I use the console.log() to view the array, the element is visible, but if I use the "array.length" function, the array element is not included :-O

$('.checkEmail, .checkPwd, .checkPwdC').bind("keyup", function() {
    //define enableSubmitButton as an array
    var enableSubmitButton = [];

    //loop each input field in the form
    $(this).parents("form").find(":input").each(function(index,data){

        //do some "if then else" and other stuff
        ...
        enableSubmitButton.push(true);
        ...

        // Now I make a request to the server with Ajax
        var xmlhttp = new XMLHttpRequest();                                
        xmlhttp.onreadystatechange = function() {
            if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
                if(xmlhttp.responseText == "doesntExist"){
                    enableSubmitButton.push(true);
                }else{
                    enableSubmitButton.push(false);
                }
            }
        };

        //Request
        xmlhttp.open("GET", "ajax.php?ajaxCase=cuna&userName="+$('#fNewAccount input.checkAvailability').val(), true);
        xmlhttp.send();
    });

    // PROBLEM
    // and here we have the problem. To debugg, i use the console.log() function as follow
    var okForSubmit = true;

    console.log(enableSubmitButton);
    console.log("array length: "+enableSubmitButton.length);

    for(var i = 0 ; i < enableSubmitButton.length ; i++){
        if(enableSubmitButton[i] == false){
            okForSubmit = false;
        }
        var newTime = Math.floor(Math.random() * (100 - 1 + 1)) + 1;
        console.log(i+" - "+enableSubmitButton[i]+" - "+newTime+" - "+okForSubmit);
    }
});

Here is the console.log() output:

(4) [true, true, true, true]
0: true
1: true
2: true
3: true
4: false
length: 5
__proto__: Array(0)

array length: 4
0 - true - 54 - true
1 - true - 19 - true
2 - true - 51 - true
3 - true - 94 - true

Any Ideas?

Marc Wampfler
  • 149
  • 2
  • 10
  • Just a side issue, could you explain why you make a request with `$('#fNewAccount input.checkAvailability')` in the URL? It does not depend in any way on the `input` element you are iterating over, so that URL would be the same URL in each iteration of the `each` loop. Should this not be `$(this)` or something similar? – trincot Oct 28 '17 at 16:11
  • Note that ```onreadystatechange``` does not run in this code, prior to your logging. That is an event handler and thus the first time it can run is after this entire event handler (of keyup) has completed. JavaScript handles one event at a time, there is no overlap. Consider comenting the entire XHR part for testing, that ```false``` comes from somewhere else. – tevemadar Oct 28 '17 at 16:16

2 Answers2

0

That is just the JavaScript console itself: it logs a mixture of live objects and text:

stuff=[true];
console.log(stuff);
stuff.push(false);

If you execute this in a JavaScript console, you will see something like

> (1) [true]

Which comes from the log, before the push. But if you open the ">" part, it will show the current state of the object, including the result of the push:

(1) [true]
 0: true
 1: false
 length: 2

So the (1) [true] part is just text, it will not get updated, but the object gets evaluted when you "open" its details (using the small arrow). If you push something again, nothing will change (on the screen of course), but closing-reopening the details can be used to evaluate the object again, etc.

tevemadar
  • 12,389
  • 3
  • 21
  • 49
  • The `console.log` output cannot be relied upon. At the time the `console.log` is made, the Ajax callbacks have not yet been made so those values are not in the array at that moment, even though the console shows them. But that is because `console.log` only logs the array reference synchronoulsy, the content is retrieved asynchronously and thus can be misleading. – trincot Oct 28 '17 at 20:07
  • An AJAX call is just too much magic to show: yes, the "dropdown" version of the object is actually a debugger output. That push results in the same effect what OP is asking about. – tevemadar Oct 28 '17 at 21:28
0

This is a typical asynchronicity problem: the HTTP request will only come with the response (calling the callback) after all your other code has finished running.

It is misleading that the console shows the element in the array, but that is because the console receives only the reference of the array, and once you look into it, the HTTP response has already been processed, and so you see the content in the console.

To really see what the array content is at the moment the console.log call is executed, serialise the array like this:

console.log(JSON.stringify(enableSubmitButton));

Now you will see it is actually empty.

In order to fix the base problem you could make use of the promise that jQuery returns for the Ajax methods it exposes (which are anyway easier to use than httpRequest). Then you would first collect all those promises in an array, and then call $.when on them. The callback of this $.when call will only execute when you have received all the Ajax responses, and only then you can safely create your enableSubmitButton array.

Here is a simplified proof of concept:

$('button').click(function f(e) {
    e.preventDefault();
    var $inputs = $(this).parents("form").find(":input");
    var promises = $inputs.map(function(index, data){
        // Make your request with jQuery, which returns a promise
        // I use a test URL here; replace with your own
        var url = "https://jsonplaceholder.typicode.com/posts/" + (index+1);
        return $.get(url).then(function(response) {
            return response == "doesntExist"; // a boolean
        });
    }).get(); // array of promises
    // When all promises have resolved, only then get your array
    $.when.apply($, promises).then(function () {
        // Get the arguments -- which are the values returned by the `then` callbacks:
        var enableSubmitButton = [].slice.call(arguments);
        console.log('result', enableSubmitButton);
    });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form>
    <input value="100"><input value="abc">
    <button>Click me</button>
</form>
trincot
  • 317,000
  • 35
  • 244
  • 286
  • The whole mumbo-jumbo is totally pointless (plus the snippet does not work for me), original content was logged already: the "(4) [true, true, true, true]" part would never change, just OP failed to spot the "length: 5" in the active part of the log. – tevemadar Oct 28 '17 at 20:00
  • The snippet works for me. The `console log` output is misleading because also *that* happens asynchronously. There really is an asynchronicity problem. At the moment of `console.log(enableSubmitButton);` the array does ***not*** contain the values added in the Ajax response. This would be evident if it were logged as `console.log(enableSubmitButton.join());`. This is more than only a `length` problem. – trincot Oct 28 '17 at 20:05
  • Ok, the snippet works fine in recent desktop browsers (Chrome, Firefox), it did not work for me on iOS Safari. – tevemadar Oct 28 '17 at 21:19
  • Console.log does work synchronously, it stringifies and prints the state of the object at the time of calling. The possibility to check objects afterwards is not related to the console itself, but to the debugger. And the two kind of outputs are consistent separately, even in the question. First output says the array has 4 elements and lists 4 true-s. Then OP checked the object "again", and it printed 5 elements, and a "length: 5" afterwards. It is there, in OP's question, just he concluded there was a state when there were 5 elements in the array, but length reported 4. No, there was not. – tevemadar Oct 28 '17 at 21:23
  • `console.log` does not work synchronously (although there is no standard for this) and it does not stringify the result to a plain string. Instead passes a reference that is presented in the debugger as a collapsible interface where nested parts are retrieved asynchronously. See for instance [here](https://stackoverflow.com/questions/24175017/google-chrome-console-log-inconsistency-with-objects-and-arrays) and [here](https://stackoverflow.com/questions/10040856/synchronous-console-logging-in-chrome). It is impossible that the array in this case would have the 4 elements that are added later. – trincot Oct 28 '17 at 21:51
  • Only the single ```false``` at the end comes from the event handler. The 4 ```true```-s come from the ```enableSubmitButton.push(true);``` in the ```each``` loop (preceding the entire XHR part). The ```each``` loop works with a callback, but it is totally synchronous construct and its result, the neverchanging ```(4) [true, true, true, true]``` is logged synchronously after the loop ends. After some time, one of the ```onreadystatechange```-s produced the ```false``` element at the end. – tevemadar Oct 28 '17 at 22:42
  • Sure, but I'd rather say that the `push` that is made outside of the request callback is useless, ... it is the one within the request callback that is really adding value. Otherwise it makes no sense to make a request at all. From what the OP's *intentions* seem to be, they need to realise that they cannot use that information in a synchronous way. Note that the request callback is also executed as many times as the `each` loop iterates, so there will be more than one (async) push happening there. – trincot Oct 28 '17 at 23:14