0

Problem:

For a student, I would like to fetch latest marks. It's possible that they have some provisional and some accepted marks. Provisional can be edited whereas accepted can't be. First I store by default latestResult in the variable this can either be provisional or accepted. Next, I am trying to run a search for all the results to check the latest accepted. Finally, I would like to know at which index of i does the accepted result exist. However, when I do console.log in .done then value of i always comes as -1 and when I console.log outside the for loop then it comes as blank.

I am looking for any suggestions through which it is possible to access the value of instance i from .done, outside the for loop.

Code

var accepted = 0;
for (var i = res.length-1; i >= 0; i--){
    connectionResult = res[i];
    //latest result
    latestResult = res[res.length-1];
    
    var idNew =  connectionResult.id;
    var filterNew = ['result', 'is', idNew];
    var recordNew = 'acceptedresult';
    
    getData({
        type: recordNew,
        filter: filterNew,
        fields: [
            'id'
        ]
    }).done(function (items) {
        if (items.length > 0) {
            accepted = 1;
            break; //if accepted is one then stop getData 
        }
    });
} //end for

if(accepted == 0){
    var editResultConfirm = confirm("Would you like to edit the Result ?");

    if (editResultConfirm == true) {
        window.open(editResultUrl,'_blank');
    }
}else{
    alert("You have an accepted result");
    window.open(finalResultUrl,'_blank');
}
colourtheweb
  • 727
  • 2
  • 13
  • 28
  • add a const idx = i before getData and use idx instead of i, or force immediate evaluation of i in the callback function somehow (the function has only been declared and not called, so i has not been evaluated yet). var i is being hoisted. It is like having var i before the for loop, so by the time done is called the value of i has been decremented to -1 – user120242 Jul 14 '20 at 23:31
  • Does this answer your question? [How to get outer loop index inside anonymous function call?](https://stackoverflow.com/questions/21010963/how-to-get-outer-loop-index-inside-anonymous-function-call) – user120242 Jul 14 '20 at 23:38
  • or use `let i = res.length-1` instead of var i (let can be scoped to blocks, while var gets hoisted) – user120242 Jul 14 '20 at 23:39

2 Answers2

1
var check = 0;
for(var i = ...

is equivalent to

var check = 0;
var i;
for(i = ...

The i variable isn't scoped to the for loop, because var isn't block scoped and gets hoisted, so the value of i used by the callback function has been decremented to -1 by the time it is evaluated inside the callback.

You can use let, which is block scoped and doesn't get hoisted:

var check = 0;
for (let i = res.length-1; i >= 0; i--){
    connectionResult = res[i];
    //latest result
    latestResult = res[res.length-1];
    
    var idNew =  connectionResult.id;
    var filterNew = ['result', 'is', idNew];
    var recordNew = 'acceptedresult';
    
    getData({
        type: recordNew,
        filter: filterNew,
        fields: [
            'id'
        ]
    }).done(function (items) {
        if (items.length > 0) {
            check = i;
            console.log("check: ",i);
            //break;
        }
    });
} //end for

console.log(check); //display empty

Or force immediate evaluation of i (and enclose a scope with the evaluated value (using a function closure here)):

var check = 0;
for (var i = res.length-1; i >= 0; i--){
  (function(i){
    connectionResult = res[i];
    //latest result
    latestResult = res[res.length-1];
    
    var idNew =  connectionResult.id;
    var filterNew = ['result', 'is', idNew];
    var recordNew = 'acceptedresult';
    
    getData({
        type: recordNew,
        filter: filterNew,
        fields: [
            'id'
        ]
    }).done(function (items) {
        if (items.length > 0) {
            check = i;
            console.log("check: ",i);
            //break;
        }
    });
  )(i); // force immediate evaluation of value of i, scoped to this function closure
} //end for

console.log(check); //display empty
user120242
  • 14,918
  • 3
  • 38
  • 52
  • Thanks for the clear explanation, it does make sense! Part of my question was also about being able to access the value of `check` from `.done` outside the `for loop`. That sadly still doesn't happen, I am getting 0. Do you know if that's possible to access ? – colourtheweb Jul 15 '20 at 07:07
  • Key point, is that check must be ready before you try to use it, by definition. Any code that tries to use it must know to wait for it. You need to hook the callback or the Promise returned before you try to access the check variable. What you should be doing is returning a Promise wrapping your check variable, and extracting check from there. Any code that tries to use check needs to wait for that Promise to resolve, or at minimum not execute until after the done callback finishes (such as by calling the code that uses check at the end of the done callback). – user120242 Jul 15 '20 at 07:09
  • It's difficult to say what you should actually be doing, because the code is oversimplified and doesn't show what you actually need. console.log(check) could be placed inside done, or you could return the Promises from done calls and `Promise.all([array of Promises]).then(()=>console.log(check))`, or convert the whole thing to async/await – user120242 Jul 15 '20 at 07:13
  • An additional issue, is why are you trying to access check? I can't think of a use case for which it would be useful. getData() could return in any order depending on when the requests return, and check could jump from 3 to 0 to 10 to 50 to 6 to 1 if the I/O takes longer or shorter, and depending on where you are in the event loop it could be any of those values. It can't really possibly be useful to you? – user120242 Jul 15 '20 at 07:19
  • Well, what I want to do is overwrite the value of `latestResult` if I get any result from the search with a correct instance of `i`, so `latestResult = res[check]`. And knowing that it can be accessed outside the for loop is useful because afterward, I can perform further operations that has to be done on correct `latestResult` value. For e.g: if the `latestResult` can be edited or not. – colourtheweb Jul 15 '20 at 07:27
  • So if I understand correctly, you want it to try to do something every time check is modified? Or you just want check to be the latest returned result any time you check for it (which is already true as-is?) The first would be to just have the callback send an event or do the work when it updates check. So triggerFunction(check) where you update check. The other is just by nature going to be correct, just that your code example doesn't represent it since it is using check's value immediately. – user120242 Jul 15 '20 at 07:30
  • If you're just trying to wait on all results being ready, you want to collect all the Promises and feed them to Promise.all, or chain the Promises or callbacks together. – user120242 Jul 15 '20 at 07:33
  • A slightly more concrete example of what you want to do, such as an actual visual output you would like to see, would help me to create a demo showing you how it could be done. – user120242 Jul 15 '20 at 07:34
  • I have edited the code. I have included an if condition after the for loop to check if the result is accepted or not. Let me know if it makes sense. – colourtheweb Jul 15 '20 at 07:49
  • So the order that done executes in is not guaranteed, so your for loop's setting of the result variables doesn't have any particular meaning? You aren't trying to use latestResult are you? It will always just be the last value of res? What are the result urls? – user120242 Jul 15 '20 at 10:28
  • It seems you have a signifiant misunderstanding of how the done callback works. It's asynchronous, and the callback is called when getData resolves and calls the callback function. The for loop does not wait for the done callback to finish, and the order that the done callback functions are called in is not guaranteed and depends on when the requests finish. Do you want it to cancel all requests if one of the results returns a value? Do you want the requests made serially or concurrently? You will also need to give more information on getData and the XHR requests exposed. – user120242 Jul 15 '20 at 10:46
  • When do you want the new code you've added to trigger and how? Javascript does not "wait" for a variable to change before continuing execution. It evaluates the if when it executes and then continues. Something _must_ trigger the execution when you want it to execute, if you need it to be triggered by something. – user120242 Jul 15 '20 at 10:47
0

The problem is you are trying to read i inside an Asynchronous function so by the time the getdata finishes/resolves the for loop is already done so i = -1 by then.

what you need to do to fix is the right way use async flow controller library like https://caolan.github.io/async/v3/

or the hackish way which you extract the get data into another function and pass i as a parameter so something along those lines

 var check = 0;
for (var i = res.length-1; i >= 0; i--){
    connectionResult = res[i];
    //latest result
    latestResult = res[res.length-1];
    
    var idNew =  connectionResult.id;
    var filterNew = ['result', 'is', idNew];
    var recordNew = 'acceptedresult';
    var params = {
        type: recordNew,
        filter: filterNew,
        fields: [
            'id'
        ]
    };
    hackishGetData(params, 1 );
} //end for

function hackishGetData(params, i){
      getData(params).done(function (items) {
        if (items.length > 0) {
            check = i;
            console.log("check: ",i);
            //break;
        }
    });
}

I don't recommend the hackish way.

mena happy
  • 216
  • 1
  • 5