For of loops in Javascript one loop to rule them all
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.