Should we use _.foreach() or better the native for of loop in TypeScript
I don't have any experience with typescript beyond my reading but I do have quite a bit of experience with ES6/ES2015. for of
was and still is part of the ES2015 spec which was finalized. I would read this article on for of
from MDN.
Here are some similarities and differences of for of
and forEach
(and these are just as far as I have found and know of currently):
forEach
in lodash works on collections that are Arrays, Objects, or Strings.native
forEach
works on Arrays, Maps, and Sets.for of
works on all Iterables: Arrays, Strings, TypedArrays, Maps, Sets, DOM collections, and generators.
I would read this chapter on for of
from Exploring ES6 (Exploring ES6 is a great read. It's very thorough. It's free online as well.) Some things from it that stand out to me as different about for of
that aren't in forEach
.
break and continue work inside for-of loops
break
and continue
aren't exposed in forEach
. The closest thing you can get to continue
in forEach is using return
which is actually pretty much the same thing. As for break
though I see no alternative (but don't discount lodash, because most things that need breaks like finding and returning a single item are already covered in much of lodash's library).
It should also be noted that the await
keyword from async/await is usable inside for of
where as forEach makes it quite a bit harder to stop the surrounding block from waiting on Promises awaited within the forEach block however it is possible to use forEach
although using a map
or reduce
may make awaiting much simpler than forEach (depending on your familiarity with those functions). Below is three separate implementations of awaiting promises both sequentially and in parallel using, for of
, forEach
, and reduce
and map
just to see the possible differences.
const timeout = ms => new Promise(res => setTimeout(() => res(ms), ms));
const times = [100, 50, 10, 30];
async function forOf() {
console.log("running sequential forOf:");
for (const time of times) {
await timeout(time);
console.log(`waited ${time}ms`);
}
console.log("running parallel forOf:");
const promises = [];
for (const time of times) {
const promise = timeout(time).then(function(ms) {
console.log(`waited ${ms}ms`);
});
promises.push(promise);
}
await Promise.all(promises);
};
async function forEach() {
console.log("running sequential forEach:");
let promise = Promise.resolve();
times.forEach(function(time) {
promise = promise.then(async function() {
await timeout(time);
console.log(`waited ${time}ms`);
});
});
await promise;
console.log("running parallel forEach:");
const promises = [];
times.forEach(function(time) {
const promise = timeout(time).then(function(ms) {
console.log(`waited ${ms}ms`);
});
promises.push(promise);
});
await Promise.all(promises);
};
async function reduceAndMap() {
console.log("running sequential reduce:");
const promise = times.reduce(function(promise, time) {
return promise.then(async function() {
await timeout(time);
console.log(`waited ${time}ms`);
});
}, Promise.resolve());
await promise;
console.log("running parallel map:");
const promises = times.map(async function(time) {
const ms = await timeout(time)
console.log(`waited ${ms}ms`);
});
await Promise.all(promises);
}
forOf().then(async function() {
await forEach();
await reduceAndMap();
}).then(function() {
console.log("done");
});
With Object.entries
which arrived in ES2017 you can even iterate objects own enumerable properties and values with ease and accuracy. If you want to use it now you can with one of the polyfills here. Here's an example of what that would look like.
var obj = {foo: "bar", baz: "qux"};
for (let x of Object.entries(obj)) { // OK
console.log(x); // logs ["foo", "bar"] then ["baz", "qux"]
}
and here's an implementation with a quick polyfill I wrote. You would normally use array destructuring as well which would seperate the key and value into it's own variables like this:
var obj = {foo: "bar", baz: "qux"};
for (let [key, val] of Object.entries(obj)) { // OK
console.log(key + " " + val); // logs "foo bar" then "baz qux"
}
You can also use Object.entries
with forEach
like so:
var obj = {foo: "bar", baz: "qux"};
console.log("without array destructuring");
Object.entries(obj).forEach((x) => { // OK
const key = x[0], val = x[1];
console.log(key + " " + val); // logs "foo bar" then "baz qux"
});
console.log("with array destructuring");
Object.entries(obj).forEach(([key, val]) => { // OK
console.log(key + " " + val); // logs "foo bar" then "baz qux"
});
forEach
's first argument defaults to the type of functionality you would get from let
in a for
or for of
loop which is a good thing. What I mean by that is if there is anything asynchronous going on inside the variable for that iteration will be scoped to just the particular part of that loop. This property of forEach is not really to do with let, but with scope and closures of functions in JavaScript, and the alternative is due to their not being block scoping. For example see what happens here when var is used:
const arr = [1,2,3,4,5,6,7,8,9];
for(var item of arr) {
setTimeout(() => {
console.log(item);
}, 100);
}
As opposed to when let
or foreach is used.
const arr = [1,2,3,4,5,6,7,8,9];
const timeout = 100;
console.log('for of');
for(let item of arr) {
setTimeout(() => {
console.log(item);
}, timeout);
}
setTimeout(() => {
console.log('foreach');
arr.forEach((item) => {
setTimeout(() => {
console.log(item);
}, timeout);
})
}, timeout*arr.length);
Again, I will note the difference between using var
and using let
or foreach
. The difference is that var's variable is hoisted up to the top of the function scope (or file if it's not in a function) and then the value is reassigned for that whole scope, so the loop reaches its end and assigns item
for the last time and then every settimeout
function logs that last item
. Whereas with let
and foreach
the variable item
does not get overwritten, because item
is scoped to the block (when let is used) or the function (when foreach is used).
Between forEach
and for of
you just need to decide which one is best for the current job (e.g. Do you need break
s or need to use Maps, Sets or Generators use for of
). Besides that I feel like there aren't particularly strong reasons for either on collections they both operate on with their core functionalities. Also when dealing with collections that can use either forEach
or for of
it's mainly just up to personal preference as they do the same thing at about the same speed (and the speeds could change at any time according to the interpreter). I feel the particular advantages of lodash are for its other various functions which could actually save you a lot of time from writing the code yourself like map, reduce, filter, and find. Since you feel most comfortable writing for of
I suggest you continue writing it that way but once you start writing in lodash using its other functions you may start to feel more comfortable writing it the lodash way.
Edit:
Looking over your code I noticed an error with your list creation. At the end you just had .split()
and you should have had .split(",")
. You were creating a list of length 1 of the whole string and iterating one time on that string that is why the bench marks were so similar. I reran the tests. Here they are. I still wouldn't worry about the performance that much it seems to change every time it's ran.
I can't comment on lodash, I haven't used it. But below is some background that may help.
'For of' was introduced in TypeScript 1.5 for looping around each element in e.g. an array list. If you examine the JS output (and depending on if you are targeting ECMA Script 5 or 6), you should find that in the case of ECMASCript5 the output of both the below will be identical. See this article for associated background reading and how targeting ES6/2015 will affect the output.
As for the Typescript implementation of ForEach, there is an interesting discussion over on GitHub here on this. Especially around conditional break out of loop.
for (let line of v.lineEntry) {
}
for (var _i = 0, list_1 = list; _i < list_1.length; _i++) {
}
Based on your test I added another, using the native Array.prototype.forEach
:
list.forEach(function(item) {
console.log("" + item)
});
This is infact my preferred way since it is actually much easier to type. Also its closer to other things you might want to do with array e.g. map
/filter
etc.
Note that http://jsperf.com/foreach-vs-forof/9 all three have no plausible performance difference.