Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

The for of Loop

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

One of the crazy things about JavaScript... is that there's not one good way to loop over a collection! In PHP, we have foreach, and it works perfectly. But in JavaScript, you need to create an ugly custom for loop. Well actually, there is a .forEach() function, but it only works on arrays, not other loopable things, like the Set object we'll talk about later. And, with .forEach(), there is no break if you want to exit the loop early.

That's why we've been using jQuery's $.each(). But guess what? ES2015 fixes this, finally. Introducing, the for of loop!

It looks like this: for (let repLog of data.items):

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 37
loadRepLogs() {
$.ajax({
... line 40
}).then(data => {
for (let repLog of data.items) {
this._addRow(repLog);
}
})
}
... lines 47 - 230
window.RepLogApp = RepLogApp;
... lines 232 - 233

And it's pretty easy to follow: repLog is the new variable inside the loop, and data.items is the thing we want to loop over. We're no longer passing this an anonymous function, so we can get rid of everything else. That's it. Say hello to your new best friend: the for of loop.

Let's look for the other $.each() spots and update those too! Instead, say for let fieldData of $form.serializeArray():

... lines 1 - 2
(function(window, $, Routing, swal) {
class RepLogApp {
... lines 6 - 94
handleNewFormSubmit(e) {
... lines 96 - 100
for (let fieldData of $form.serializeArray()) {
formData[fieldData.name] = fieldData.value
}
... lines 104 - 111
}
... lines 113 - 212
}
... lines 214 - 231
})(window, jQuery, Routing, swal);

Before, the anonymous function received a key and then the fieldData. But, we didn't actually need the key: the $.each() function just forced us to add it. Now, things are cleaner!

Tip

Since we are in a for loop now, we need to also update the return statement to be continue.

Make this same change in two more places: for $element of $form.find(':input'). Ah, don't forget your let or var:

... lines 1 - 4
class RepLogApp {
... lines 6 - 136
_mapErrorsToForm(errorData) {
... lines 138 - 140
for (let element of $form.find(':input')) {
... lines 142 - 152
}
}
... lines 155 - 178
}
... lines 180 - 233

Then, one more below: for let $element of $elements:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 183
class Helper {
... lines 185 - 204
static _calculateWeights($elements) {
... line 206
for (let element of $elements) {
... line 208
}
... lines 210 - 211
}
}
... lines 214 - 231
})(window, jQuery, Routing, swal);

Oh, and PhpStorm is warning me because I forgot to remove one of my closing parentheses! And, we don't need that semicolon! Yay!

So, use the for of loop for everything! Well actually, that's not 100% true. for of is perfect when you want to loop over a collection of items. But, if you want to loop over an associative array... or object, and you need to know the key for each item, then you'll use for in.

Tip

Actually, you can use for of with an object, with a clever combination of Object.entries() and array destructuring!

let pets = {
  beagle: 'Bark Twain',
  poodle: 'Snuffles'
};

for (let [petKey, petName] of Object.entries(pets)) {
  console.log(petKey, petName);
}

BUT, the Object.entries() method is still experimental, and may be included in ES2017.

This is the one limitation of for of: it gives you the value of the item you're looping over, but not its key, or index. In fact, if try to use for of with an object, you'll get an error.

Leave a comment!

6
Login or Register to join the conversation

You can absolutely use const instead of let in "for of" loop.

Reply

Hey plashenkov!

Ah, you're totally right! It's not something I had even thought of - const isn't allowed for a traditional "for", but definitely is for "for of". Thanks for the note!

Cheers!

1 Reply
Default user avatar

In _mapErrorsToForm the `return` should be `continue`. With `return` the loop will stop iterating after first valid form input as the method executions is finished.

Reply

Hey @Marcin!

Ah, you’re totally right! I’ll have is update that so it doesn’t mess anyone up.

Thanks for reporting this!

1 Reply

Hey @Marcin

Actually, in order to stop the loop you have to specifically return false otherwise the return statement will work just as a continue statement

From jQuery docs: "You can stop the loop from within the callback function by returning false."

Reply

Hey MolloKhan!

Yes..... but unfortunately, in this case, we're not using a jQuery loop :). This is a native, JS for loop. So, the return will exit from the _mapErrorsToForm function. But, I know why you got confused! This function did originally use a jQuery loop (https://symfonycasts.com/screencast/javascript/js-validation-error-handling#codeblock-f4b4af62c2). At some point, we refactored it to a native for. And that is when we needed to also change that return statement.

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses Symfony 3. But, since this is a JavaScript tutorial, all the concepts work fine in newer versions of Symfony.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.2.0",
        "symfony/symfony": "3.2.*", // v3.2.14
        "twig/twig": "2.10.*", // v2.10.0
        "doctrine/orm": "^2.5", // v2.7.1
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.2
        "symfony/swiftmailer-bundle": "^2.3", // v2.4.2
        "symfony/monolog-bundle": "^2.8", // v2.12.1
        "symfony/polyfill-apcu": "^1.0", // v1.3.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.19
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "friendsofsymfony/user-bundle": "~2.0@dev", // dev-master
        "doctrine/doctrine-fixtures-bundle": "~2.3", // v2.4.1
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.1
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "friendsofsymfony/jsrouting-bundle": "^1.6" // 1.6.0
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.2
        "symfony/phpunit-bridge": "^3.0" // v3.2.2
    }
}
userVoice