For of loops in Javascript one loop to rule them all

javascript Mar 17, 2020

For the longest time, for in and for were the main loops you could use to iterate over collections of things in JavaScript. Then we got some fancy array methods like forEach, map, filter, etc. It starts to get a bit confusing of when to use each loop type. For example, you can't use for in on array's, only on objects. Then, how do I loop over an object? Well, you can use for in, but only if you check hasOwnProperty or whatever, or use...

Object.keys(obj).map((key) => {
  const value = map[key];
});

Which is all weird because you have to get the keys, then grab the value, etc.

Now, we have a new thing as of ES6 called for of. It's becoming more and more used as knowledge of how to use it has grown, but there's still occasional confusion around how/when to use it. Below is a quick cheatseat of some usages of for of, one loop to rule them all.

Arrays

const arrayOfStuff = ['thing one', 'thing two', 'thing three'];

for (const thing of arrayOfStuff) {
  console.log(thing);
}

For arrays, it's pretty simple. It looks like a for in, but you can't for in an array. The point here is, the thing becomes each item in the array.

Arrays of Objects

const arrayOfObjectsOfStuff = [{ name: 'thing one' }, {name: 'thing two' }, { name: 'thing three' }];

for (const { name } of arrayOfObjectsOfStuff) {
  console.log(name);
}

Here you'll notice when iterating an array of objects, you can utilize destructuring to pull the value of the key name off of every item in the array. Note, the descructuring here uses {}'s because we're descructuring an object, vs [] in the case of desctructuring an array.

Objects

const userMap = {
  '123': 'user 1',
  '456': 'user 2',
  '789': 'user 3',
};

for (const [ id, name ] of Object.entries(userMap)) {
  console.log(id, name);
}

Things get even cooler here now, thanks to the magic of Object.entries. Object.entries returns an array of key value pairs, so in this case basically...

[
  [123, 'user 1'],
  [456, 'user 2'],
  [789, 'user 3'],
]

So, you're in one line converting the object to an array of key value arrays, and then using destructuring to get the id, and name values!

Maps

const actualMapOfUsers = new Map();

actualMapOfUsers.set('123', 'user 1');
actualMapOfUsers.set('456', 'user 2');
actualMapOfUsers.set('7899', 'user 3');

for (const [id, name] of Array.from(actualMapOfUsers)) {
  console.log(id, name);
}

With ES6 Map objects, you can just use the Array.from method to convert the Map into, you guessed it, an array of key values pairs again.

Promises

const getUser = async (name) => {
  const response = await fetch(`https://api.github.com/users/${name}`);
  const json = await response.json();
  return json;
};

const arrayOfPromises = [];

const usernames = ['jcreamer898', 'kwelch', 'AlexSwensen'];
for (const user of usernames) {
  arrayOfPromises.push(getUser(user));
}

Promise.all(arrayOfPromises).then((users) => {
  for (const user of users) {
    console.log(user.name);
  }
});

The final crazy cool thing you can do is handle promises or async await inside of for of loops. In the above example, we're actually creating an array of promises which we then resolve with Promise.all, so this will add a bunch of stuff into the event loop and then once they're all resolved, call the .then on the Promise.all.

Note in this case, there's no use of async / await, so the code will transpile to much less than that of the code that would require babel polyfill, etc from using async await. That said, you probably already have a polyfill like babel installed, so alternatively you can still async/await the Promise.all with...

const main = async () => {
    const users = await Promise.all(arrayOfPromises);
};

The other option is to use await in an async function and actually await each response.

const getUser = async (name) => {
  const response = await fetch(`https://api.github.com/users/${name}`);
  const json = await response.json();
  return json;
};

const getUsers = async () => {
    const users = [];
    const usernames = ['jcreamer898', 'kwelch', 'AlexSwensen'];

    for (const name of usernames) {
      const user = await getUser(name);
      users.push(user);
    }

    return users;
};

const main = async () => {
  await getUsers();
};

In this case the code will pause and wait for each getUser response to come back before moving on to the next one.

Here is a code sandbox where you can see all of this running!

Hopefully this article helps clear up any confusion in for of loops going forward.

Tags