Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should we use _.foreach() or better the native for of loop in TypeScript

I just started to work in a new project working with TypeScript. I'm comming from another project that also worked with TypeScript. Since the native for of loop in TypeScript is avaible we decided (old project team) to use this one. Espacialy for me it was much more convenient to write the for of loop, relating to my java background.

Now in the new project they use everywhere the _.foreach() loop to iterate over arrays.

What I am wondering, is there a performance difference between the native typescript for of and the _.foreach()

i've created a little test in jsperf they seam to be more or less exactly same speed...

https://jsperf.com/foreach-vs-forof/12

TypeScript For of

for (let num: string of list){
  console.log(num);
}

In JavaScript

var list = "9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9".split();

//Transpiled TypeScript for of     | **19,937  ±5.04%
for (var _i = 0, list_1 = list; _i < list_1.length; _i++) {   
  var num = list_1[_i];   
  console.log("" + num); 
}

//lodash                           | 20,520  ±1.22%  
_.forEach(list, function(item) {
  console.log("" + item)
});

Imho i would prefer the "native" for of from TypeScript cause its more readable for me.

What do you guys suggest to use? Are there other points to use for of or better _.forEach

like image 286
Pascal Avatar asked Mar 09 '16 09:03

Pascal


2 Answers

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 breaks 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.

like image 182
John Avatar answered Sep 20 '22 06:09

John


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.

like image 27
basarat Avatar answered Sep 21 '22 06:09

basarat