Search code examples
javascriptrequirejs

RequireJs is breaking my for loop when combining files (r.js)


I'm using RequireJs and for production releases combining/minify-ing and uglify-ing it using r.js, but have run into a bug related to how it handles one of my FOR loops.

I turned off all optimizations on the r.js build so now it is only combining the code into one file, but not minifying/uglifying; however, the bug persists.

Original (simplified) Code:

var myArray = ['item1', 'apple', 'item2', 'item3', 'bread', 'etc'];

//remove items from array according to some complex logic
for (var i = 0; i < myArray.length; i++) {

  var cur = myArray[i];

  if (cur.indexOf('item') === 0) { //if starts with "item" remove it
    myArray.splice(i, 1);
    i--; //*IMPORTANT* Move index back to avoid skipping next item
    continue;
  }

  //do unrelated processing
}

//now use the array
alert(myArray);

Orig Code Output:

['apple', 'bread', 'etc']

This code works correctly as written. However, when i run it through r.js (with optimize: "none") it outputs this code which doesn't work as intended:

r.js output code:

var myArray = ['item1', 'apple', 'item2', 'item3', 'bread', 'etc'];


var _loop = function _loop(_i) {
  var cur = myArray[_i];

  if (cur.indexOf('item') === 0) {
    myArray.splice(_i, 1);
    _i--;
    return 'continue';
  }
}

for (var i = 0; i < myArray.length; i++) {
  var _ret = _loop(i);

  if (_ret === 'continue') continue;
}

alert(myArray);

r.js Code Output:

['apple', 'item3', 'bread', 'etc']

As you can see the code is nearly identical, but because the r.js moved my for loop into a function, I can't alter the index so after removing/splicing a value and moving back the index, that change to the index is lost entirely and the next item in the array is skipped.

Work Arounds

I could loop the index back to front so I don't need to alter the index, but I'm performing other calculations that need to be done in order (front-to-back). Plus I'm worried about this bug sneaking into my code in the future.

Questions

Is this how r.js is supposed to work (did i miss a simple setting)?

Am I coding in an anti-pattern and should change my approach?

Thanks!


Solution

  • Update

    As discussed in the comments, the solution is to pull the declaration of i out of the for loop structure:

    var i;
    for (i = 0; i < myArray.length; i++) {
        // ...
    }
    

    Opinion: Since there is no functional difference between the two ways of declaring i, it's funny that they are interpreted as being different by r.js.


    You could of course also go with Array.prototype.filter to entirely dodge the problem:

    var myArray = ['item1', 'apple', 'item2', 'item3', 'bread', 'etc'];
    myArray = myArray.filter(item => ! /^item/.test(item));
    console.log(myArray);

    Performing additional logic could also be done both before and after filtering by using Array.prototype.forEach:

    var myArray = ['item1', 'apple', 'item2', 'item3', 'bread', 'etc'];
    
    /** @return {boolean} */
    function filterFn(item, index) {
      console.log(`Doing additional stuff with: myArray[${index}] = ${item}`);
      return ! /^item/.test(item);
    }
    
    myArray = myArray.filter(filterFn);
    
    myArray.forEach((item, index) => {
      console.log(`After: myArray[${index}] = ${item}`);
    });