connexo
connexo

Reputation: 56753

Using switch case the break inside a case ends the outer for...of loop iteration

I have the following function defined and run on DOMContentLoaded (among others, not relevant here):

function dependentControls() {
  const dependers = [...document.querySelectorAll('[data-depends-on]')]
  for (const depender of dependers) {
    let dependency = document.getElementById(depender.dataset.dependsOn)
    if (dependency) {
      let dependencyDetails = {
        prop: null,
        state: null
      }
      switch (dependency.type) {
        case 'checkbox':
          {
            dependencyDetails.prop = 'checked'
            dependencyDetails.state = false
            break // this break exits the current for loop iteration
          }
        case 'text':
          {
            dependencyDetails.prop = 'value'
            dependencyDetails.state = ''
            break // this break exits the current for loop iteration
          }
        default:
          console.log('default case')
      }
      console.log("switch...end") // this line is never reached
      depender.disabled = !dependency[dependencyDetails.prop]
      dependency.addEventListener('change', () => {
        console.log('dependancy changed')
        depender.disabled = dependency[dependencyDetails.prop] === dependencyDetails.state
      })
    }
  }
  console.log('for...end')
}

document.addEventListener('DOMContentLoaded', dependentControls);
<div>
  <input type="checkbox" id="fooBar" />
  <label for="fooBar">dependency</label>
</div>
<hr />
<div>
  <label for="fooBaz">depender</label>
  <input type="text" id="fooBaz" data-depends-on="fooBar" value="depender" />
</div>

This is the relevant part of the transpiled code:

function _toConsumableArray(arr) {
  if (Array.isArray(arr)) {
    for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) {
      arr2[i] = arr[i];
    }
    return arr2;
  } else {
    return Array.from(arr);
  }
}

var dependentControls = function dependentControls() {
  var dependers = _toConsumableArray(document.querySelectorAll('[data-depends-on]'));
  
  var _iteratorNormalCompletion5 = true;
  var _didIteratorError5 = false;
  var _iteratorError5 = undefined;

  try {
    var _loop5 = function _loop5() {
      var depender = _step5.value;

      var dependency = document.getElementById(depender.dataset.dependsOn);

      if (dependency) {
        var dependencyDetails = {
          prop: 'checked',
          state: false
        };

        switch (dependency.type) {
          case 'checkbox':
            {
              dependencyDetails.prop = 'checked';
              dependencyDetails.state = false;
              return "break";
            }

          case 'text':
            {
              dependencyDetails.prop = 'value';
              dependencyDetails.state = '';
              return "break";
            }

          default:
        }

        console.log("switch...end"); // this line is never reached

        depender.disabled = !dependency[dependencyDetails.prop];
        dependency.addEventListener('change', function() {
          depender.disabled = dependency[dependencyDetails.prop] === dependencyDetails.state;
        });
      }
    };

    _loop4: for (var _iterator5 = dependers[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
      var _ret2 = _loop5();

      switch (_ret2) {
        case "break":
          break _loop4;

        default:
          if (_typeof(_ret2) === "object") return _ret2.v;
      }
    }
  } catch (err) {
    _didIteratorError5 = true;
    _iteratorError5 = err;
  } finally {
    try {
      if (!_iteratorNormalCompletion5 && _iterator5.return != null) {
        _iterator5.return();
      }
    } finally {
      if (_didIteratorError5) {
        throw _iteratorError5;
      }
    }
  }

  console.log("for...end");
}

document.addEventListener('DOMContentLoaded', dependentControls);
<div>
  <input type="checkbox" id="fooBar" />
  <label for="fooBar">dependency</label>
</div>
<hr />
<div>
  <label for="fooBaz">depender</label>
  <input type="text" id="fooBaz" data-depends-on="fooBar" value="depender" />
</div>

This is my .babelrc:

{
  "presets": [
    [ "@babel/preset-env", {
      "targets": {
        "browsers": [ "last 2 versions", "ie >= 11" ]
      }
    }]
  ]
}

I'm using Babel 7 to transpile the code and the latest webpack to bundle it. When I paste this code in the console and call the function, the problem does not occur. When I paste this code in http://babeljs.io/repl and paste the resulting code in the console, it also works.

By the looks of it, this has been reported in April already, with noone commenting on the issue so far:

https://github.com/babel/babel/issues/7765

Nonetheless I've also filed it:

https://github.com/babel/babel/issues/8709

Upvotes: 4

Views: 151

Answers (2)

connexo
connexo

Reputation: 56753

Turns out that is really a bug introduced in Babel 7.

The reason it wasn't reproducable in the Babel REPL is that the official REPL is still on Babel 6.2.6. The 7.0 REPL is currently available here:

https://babeljs.io/repl/build/master

Babel 7 transpiles break to return "break"; if the break occurs in a case block that is wrapped in curly braces:

This works as expected with Babel 7:

switch (a) {
  case 1:
    /** some code **/
    break
  default:
}

This creates the problem:

switch (a) {
  case 1: { // <-- wrapping the case ...
    /** some code **/
    break
  } // <-- ... makes Babel go wrong
  default: {}
}

Wrapping the case in {} makes Babel transpile the break to return "break"; - which is clearly a bug on Babel side.

Temporary solution:

Just don't wrap case instructions in a new block context {} for now if you don't have to (the new block context in my original code was only for readability, so unnecessary anyway). Will update the answer when Babel has fixed that.

Upvotes: 2

rafaelcastrocouto
rafaelcastrocouto

Reputation: 12161

The code is working just fine, the console.log("switch end") is executed as expected.

Make sure your babel version is updated.

You can check it working here: https://codepen.io/rafaelcastrocouto/pen/LJrRqj

'use strict';

function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }

function dependantControls() {
  var dependers = [].concat(_toConsumableArray(document.querySelectorAll('[data-depends-on]')));
  var _iteratorNormalCompletion = true;
  var _didIteratorError = false;
  var _iteratorError = undefined;

  try {
    var _loop = function _loop() {
      var depender = _step.value;

      var dependency = document.getElementById(depender.dataset.dependsOn);
      if (dependency) {
        var dependencyDetails = {
          prop: null,
          state: null
        };
        switch (dependency.type) {
          case 'checkbox':
            {
              dependencyDetails.prop = 'checked';
              dependencyDetails.state = false;
              break; // this break exits the current for loop iteration
            }
          case 'text':
            {
              dependencyDetails.prop = 'value';
              dependencyDetails.state = '';
              break; // this break exits the current for loop iteration
            }
          default:
            console.log('default case');
        }
        console.log("switch end"); // this gets never executed!
        depender.disabled = !dependency[dependencyDetails.prop];
        dependency.addEventListener('change', function () {
          console.log('dependancy changed');
          depender.disabled = dependency[dependencyDetails.prop] === dependencyDetails.state;
        });
      }
    };

    for (var _iterator = dependers[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
      _loop();
    }
  } catch (err) {
    _didIteratorError = true;
    _iteratorError = err;
  } finally {
    try {
      if (!_iteratorNormalCompletion && _iterator.return) {
        _iterator.return();
      }
    } finally {
      if (_didIteratorError) {
        throw _iteratorError;
      }
    }
  }

  console.log('for...end');
}

document.addEventListener('DOMContentLoaded', dependantControls);
<div>
  <input type="checkbox" id="fooBar" />
  <label for="fooBar">dependency</label>
</div>
<hr />
<div>
  <label for="fooBaz">depender</label>
  <input type="text" id="fooBaz" data-depends-on="fooBar" value="depender" />
</div>

Upvotes: 0

Related Questions