For loops in JavaScript (vs _.times)

You can watch me go through this topic here:

From time to time I still see a for loop in JavaScript codebases. Linters are frequently angry about them.
There are use-cases, like async operations that have to happen in order, that are an exception to this rule and provide a good excuse to use eslint-disable comment.
Nonetheless, in most cases there is usually a more human-readable way of looping. If you need to iterate over an array most developers intuitively reach for .map .forEach .reduce and so on. But when they want to iterate a number of times, a lot of them still default to a for loop.

To give an example, let's assume we want a function that will create a number of some kind of an object. This might be a part of data initialization, or maybe part of a test-util.
We could define it like so:

expect(createObjects(2)).toEqual([{ id: 0 }, { id: 1 }]);
expect(createObjects(3)).toEqual([{ id: 0 }, { id: 1 }, { id: 2 }]);

and implement it like so:

const createObjects = (howMany: number) => {
  const results = [];
  for (let i = 0; i < howMany; i += 1) {
    results.push({ id: i });
  }
  return results;
};

It seems fine, but there is a few things to track and setup. You create an empty array that you need to mutate and then return. You need to setup the loop itself - make sure the increment is done correctly, and that the edge condition is handled correctly as well. All in all - the noise to signal ratio is high.

Let's see how we could do this with times function:

const createObjectsTimes = (howMany: number) =>  
  times(howMany, (i) => ({ id: i }));

This code gets straight to the point - the only thing you need to worry about is the logic for what should be the shape of the object. Obviously this is a simplified example but you can imagine that maybe you would generate some randomized data, or maybe you would create some based on the index:

times(howMany, (i) => ({ id: i, isActive: i % 2 === 0 }));

You might also need to call an external function, or maybe even perform an async operation. You might need to actually create an array and maybe push to it as well, at this point you have to start differentiating between two variables that point to two different arrays. The amount of code will start increasing but it's better if it's not nested with old-school clunky for loop.

Some people are hesitant using external tooling like underscore/lodash. They usually worry about increasing the build size.
In my opinion this should be a very rare concern - if you are building an application the maintenance cost should usually be a much bigger concern than a few kbs here or there.

A good example where it might be worth to NOT use any external dependencies are lambda functions.
It's significantly easier to set them up, debug, update if you only use nodejs and aws-sdk APIs. If your function is just tens of lines of code (or sometimes even less than that), it might be a good idea to start with self-contained module.

In that case you could easily build your own version of times, for example:

const myTimes = <TResult>(
  howMany: number,
  cb: (i: number) => TResult
): TResult[] =>
  new Array(howMany)
    .fill(1)
    .map((_, i) => i)
    .map(cb);

or even smaller with pure-js (which you might end up using to skip build step altogether):

const myTimes = (howMany, cb) =>  
  new Array(howMany)  
  .fill(1)  
  .map((_, i) => i)  
  .map(cb);

This decision is worth putting under constant evaluation - things almost always start small, but frequently turn much bigger. Once you start adding more and more handcrafted helpers, or even just a couple but very complex ones, consider adding a build and bundle step. Something like our own https://github.com/xolvio/cdk-typescript-tooling takes care of a lot of issues related to those steps. It will also treeshake the code so your final bundle should not increase that much in size.

Thanks!

Let me know if you have any questions or thoughts in the comments below.


Let us help you on your journey to Quality Faster

We at Xolvio specialize in helping our clients get more for less. We can get you to the holy grail of continuous deployment where every commit can go to production — and yes, even for large enterprises.

Feel free to schedule a call or send us a message below to see how we can help.

User icon
Envelope icon

or

Book a call
+
Loading Calendly widget...
  • Add types to your AWS lambda handler

    Lambdas handlers can be invoked with many different, but always complex, event arguments. Add to that the context, callback, matching return type and you basically start listing all the different ways that your function can fail in production.

  • How to expose a local service to the internet

    From time to time you might need to expose your locally running service to the external world - for example you might want to test a webhook that calls your service. To speed up the test/development feedback loop it would be great to be able to point that webhook to your local machine.

  • Sharing scalars between modules with Chimp

    Simplify your Chimp-based project even further with a new @predefined Scalar directive.