1

While waiting for the back end devs to implement a "cancel all" feature, which cancels all tasks tracked by the back end, I am attempting to makeshift it by cancelling each individual task. The cancel REST service accepts an ID in the form of a data object {transferID: someID}.

I use a FOR loop to iterate over an array of IDs that I have stored elsewhere. Anticipating that people MAY end up with dozens or hundreds of tasks, I wanted to implement a small delay that will theoretically not overflow the number of HTTP requests the browser can handle and will also reduce a blast of load on the back end CPU. Here is some code with comments for the purpose of this discussion:

ta.api.cancel = function (taskArray, successCallback, errorCallback) {
// taskArray is ["task1","task2"]

// this is just the latest attempt. I had an attempt where I didn't bother
// with this and the results were the same. I THOUGHT there was a "back image"
// type issue so I tried to instantiate $.ajax into two different variables.
// It is not a back image issue, though, but one to do with setTimeout.
ta.xhrObjs = ta.xhrObjs || {}; 


for (var i = 0; i < taskArray.length; i++) {
    console.log(taskArray); // confirm that both task1 and task2 are there.

    var theID = taskArray[i];
    var id = {transferID: theID}; // convert to the format understood by REST

    console.log(id); // I see "task1" and then "task2" consecutively... odd,
    // because I expect to see the "inside the setTimeout" logging line next

    setTimeout(function () {
      console.log('inside the setTimeout, my id is: ')
      console.log(id.transferID);
      // "inside the setTimeout, my id is: task2" twice consecutively! Y NO task1?

      ta.xhrObjs[theID] = doCancel(id);
    }, 20 * i);
  }

  function doCancel(id) {
    // a $.Ajax call for "task2" twice, instead of "task1" then "task2" 20ms
    // later. No point debugging the Ajax (though for the record, cache is
    // false!) because the problem is already seen in the 'setTimeout' and
    // fixed by not setting a timeout.
  }
}

Thing is: I know setTimeout makes the containing function execute asynchronously. If I take out the timeout, and just call doCancel in the iterator, it will call it on task1 and then task2. But although it makes the call async, I don't understand why it just does task2 twice. Can't wrap my head around it.

I am looking for a way to get the iterator to make the Ajax calls with a 20ms delay. But I need it to call on both! Anybody see a glaring error that I can fix, or know of a technique?

Greg Pettit
  • 10,749
  • 5
  • 53
  • 72
  • what is `jobArray`... should that be `taskArray` ? – CrayonViolent May 29 '15 at 21:02
  • 1
    Declare your variables at the start of the function they are scoped to. Not only is that how JS will run your program (hoisting) it should help you see what is up (closure on var id). – Malk May 29 '15 at 21:03
  • 1
    i think the immediate reason you have issues is when you use a variable within `setTimeout`, it doesn't "freeze" the variable to the value at the time `setTimeout` was declared. Instead, it will eval the var when the `setTimeout` callback call is actually made. You need to create a closure http://stackoverflow.com/questions/10217536/how-to-pass-a-variable-into-a-settimeout-function – CrayonViolent May 29 '15 at 21:06
  • 1
    Here's some more possibilities for fixing the `setTimeout`: http://stackoverflow.com/a/17557081/188246 – David Sherret May 29 '15 at 21:12
  • Thanks, Crayon-- an artifact of making the real function a bit more "digestible" for SO-- it was crufty with other unimportant stuff. Thanks again! – Greg Pettit May 30 '15 at 01:06

2 Answers2

1

You must wrap your function setTimeout and pass the id variable into it, like this:

(function(myId, i) {
    setTimeout(function () {
        console.log('inside the setTimeout, my id is: ', myId);
    }, 20 * i);
}(theId, i));
Jose Mato
  • 2,709
  • 1
  • 17
  • 18
1

This pattern does not create a unique variable1 for each instance of the loop as one might expect.

function () {
    for (var i = 0; i < length; i++) {
        var variable1;
     }
}

In javascript variables are "hoisted". To quote Mozilla:

"Because variable declarations (and declarations in general) are processed before any code is executed, declaring a variable anywhere in the code is equivalent to declaring it at the top."

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var

So it should be re-written as:

function () {
    var variable1;
    for (var i = 0; i < length; i++) {
    }
}

What this means is that after the loop has finished, any asynchronous callbacks that reference this variable will see the last value of the loop.

Malk
  • 11,855
  • 4
  • 33
  • 32
  • There's seemingly more to it than that. If I declare a variable outside the for loop, and then update it inside the for loop, the last value is still used for the setTimeout. And I need to update the variable within the for loop because it changes on each iteration. If I had a way to update it before going into the for loop, I think it would be fine; however, the whole point of the iterator is to get the new value at index. – Greg Pettit Jun 01 '15 at 17:15
  • You are right. You need to handle this issue to get what you need. Check Jose Mato's answer. It uses an immediately executed function expression that "locks in" a unique variable for each iteration. – Malk Jun 01 '15 at 17:38