197

The typical way to loop x times in JavaScript is:

for (var i = 0; i < x; i++)
  doStuff(i);

But I don't want to use the ++ operator or have any mutable variables at all. So is there a way, in ES6, to loop x times another way? I love Ruby's mechanism:

x.times do |i|
  do_stuff(i)
end

Anything similar in JavaScript/ES6? I could kind of cheat and make my own generator:

function* times(x) {
  for (var i = 0; i < x; i++)
    yield i;
}

for (var i of times(5)) {
  console.log(i);
}

Of course I'm still using i++. At least it's out of sight :), but I'm hoping there's a better mechanism in ES6.

at.
  • 45,606
  • 92
  • 271
  • 433
  • 3
    Why is the mutable loop control variable an issue? Just a principle? – doldt May 26 '15 at 07:33
  • 1
    @doldt - I'm trying to teach JavaScript, but I'm experimenting with delaying the concept of mutable variables until later – at. May 26 '15 at 07:37
  • 5
    We're getting really off-topic here, but are you sure that moving on to ES6 generators (or any other new, high level concept) is a good idea before they learn about mutable variables?:) – doldt May 26 '15 at 07:38
  • 5
    @doldt - maybe, I'm experimenting. Taking a functional language approach to JavaScript. – at. May 26 '15 at 07:43
  • Use let to declare that variable in the loop. Its scope ends with the loop. – ncmathsadist Jul 23 '20 at 18:35

22 Answers22

322

Using the ES2015 Spread operator:

[...Array(n)].map()

const res = [...Array(10)].map((_, i) => {
  return i * 10;
});

// as a one liner
const res = [...Array(10)].map((_, i) => i * 10);

Or if you don't need the result:

[...Array(10)].forEach((_, i) => {
  console.log(i);
});

// as a one liner
[...Array(10)].forEach((_, i) => console.log(i));

Or using the ES2015 Array.from operator:

Array.from(...)

const res = Array.from(Array(10)).map((_, i) => {
  return i * 10;
});

// as a one liner
const res = Array.from(Array(10)).map((_, i) => i * 10);

Note that if you just need a string repeated you can use String.prototype.repeat.

console.log("0".repeat(10))
// 0000000000
Community
  • 1
  • 1
Tieme
  • 53,990
  • 18
  • 90
  • 147
  • 29
    Better: `Array.from(Array(10), (_, i) => i*10)` – Bergi Oct 16 '16 at 21:44
  • 7
    This should be the best answer. So ES6! Much awesome! – Gergely Fehérvári May 04 '17 at 17:36
  • 4
    If you don't need the iterator (i), you can exclude both the key and value to make this: `[...Array(10)].forEach(() => console.log('looping 10 times');` – Sterling Bourne Aug 29 '17 at 15:23
  • 9
    So you allocate **entire** array of N elements just to throw it away? – Kugel Nov 30 '17 at 07:28
  • 2
    Has anyone addressed the previous comment by Kugel? I was wondering the same thing – Arman Jun 26 '18 at 03:34
  • If you're using a TypeScript compiler to target ES5, [...Array(10)] will get outputted as Array.Slice(10) which will result in no iteration. I suggest using Array.Apply as suggested in the following solution to work around this issue (also works in IE 9+) stackoverflow.com/a/26707922/2035501. Note, the ES6 spread syntax doesn't seem to be an issue when using babel as a transpiler. – Andrew Jan 04 '19 at 16:58
  • 1
    This is the most functional answer. – shinzou Mar 06 '19 at 13:46
  • @Kugel, this method is very helpful; for example, when you want to `run` some `function or code-block n number of time`. – Aakash Apr 11 '19 at 02:22
  • 1
    I'm curious ... why is it not working without spread? Why is `Array(10).map((_, i) => i * 10)` not working ? – sebpiq Jan 20 '20 at 20:02
  • Here is a wrapper function for this answer in TypeScript: `export const mapN = (count: number, fn: (...args: any[]) => T): T[] => [...Array(count)].map((_, i) => fn())`. Then run `mapN(3, () => 'hello')` – Andries May 06 '20 at 12:35
  • 2
    @sebpiq Because the Array(10) function returns an empty array instance with length set to 10. The array instance is essentially allocated in memory but empty. If you try to map() over it, it will fail because the array is empty. When you try to spread it, though, the spread operator will return the same number of items as the array's length. Since the array is empty, those items are undefined (non-existent), so spread will give you 10 elements === undefined. Hence the (_, i) => {} syntax to always ignore the first (constantly undefined) parameter. – Xunnamius May 22 '20 at 03:00
173

OK!

The code below is written using ES6 syntaxes but could just as easily be written in ES5 or even less. ES6 is not a requirement to create a "mechanism to loop x times"


If you don't need the iterator in the callback, this is the most simple implementation

const times = x => f => {
  if (x > 0) {
    f()
    times (x - 1) (f)
  }
}

// use it
times (3) (() => console.log('hi'))

// or define intermediate functions for reuse
let twice = times (2)

// twice the power !
twice (() => console.log('double vision'))

If you do need the iterator, you can use a named inner function with a counter parameter to iterate for you

const times = n => f => {
  let iter = i => {
    if (i === n) return
    f (i)
    iter (i + 1)
  }
  return iter (0)
}

times (3) (i => console.log(i, 'hi'))

Stop reading here if you don't like learning more things ...

But something should feel off about those...

  • single branch if statements are ugly — what happens on the other branch ?
  • multiple statements/expressions in the function bodies — are procedure concerns being mixed ?
  • implicitly returned undefined — indication of impure, side-effecting function

"Isn't there a better way ?"

There is. Let's first revisit our initial implementation

// times :: Int -> (void -> void) -> void
const times = x => f => {
  if (x > 0) {
    f()               // has to be side-effecting function
    times (x - 1) (f)
  }
}

Sure, it's simple, but notice how we just call f() and don't do anything with it. This really limits the type of function we can repeat multiple times. Even if we have the iterator available, f(i) isn't much more versatile.

What if we start with a better kind of function repetition procedure ? Maybe something that makes better use of input and output.

Generic function repetition

// repeat :: forall a. Int -> (a -> a) -> a -> a
const repeat = n => f => x => {
  if (n > 0)
    return repeat (n - 1) (f) (f (x))
  else
    return x
}

// power :: Int -> Int -> Int
const power = base => exp => {
  // repeat <exp> times, <base> * <x>, starting with 1
  return repeat (exp) (x => base * x) (1)
}

console.log(power (2) (8))
// => 256

Above, we defined a generic repeat function which takes an additional input which is used to start the repeated application of a single function.

// repeat 3 times, the function f, starting with x ...
var result = repeat (3) (f) (x)

// is the same as ...
var result = f(f(f(x)))

Implementing times with repeat

Well this is easy now; almost all of the work is already done.

// repeat :: forall a. Int -> (a -> a) -> a -> a
const repeat = n => f => x => {
  if (n > 0)
    return repeat (n - 1) (f) (f (x))
  else
    return x
}

// times :: Int -> (Int -> Int) -> Int 
const times = n=> f=>
  repeat (n) (i => (f(i), i + 1)) (0)

// use it
times (3) (i => console.log(i, 'hi'))

Since our function takes i as an input and returns i + 1, this effectively works as our iterator which we pass to f each time.

We've fixed our bullet list of issues too

  • No more ugly single branch if statements
  • Single-expression bodies indicate nicely separated concerns
  • No more useless, implicitly returned undefined

JavaScript comma operator, the

In case you're having trouble seeing how the last example is working, it depends on your awareness of one of JavaScript's oldest battle axes; the comma operator – in short, it evaluates expressions from left to right and returns the value of the last evaluated expression

(expr1 :: a, expr2 :: b, expr3 :: c) :: c

In our above example, I'm using

(i => (f(i), i + 1))

which is just a succinct way of writing

(i => { f(i); return i + 1 })

Tail Call Optimisation

As sexy as the recursive implementations are, at this point it would be irresponsible for me to recommend them given that no JavaScript VM I can think of supports proper tail call elimination – babel used to transpile it, but it's been in "broken; will reimplement" status for well over a year.

repeat (1e6) (someFunc) (x)
// => RangeError: Maximum call stack size exceeded

As such, we should revisit our implementation of repeat to make it stack-safe.

The code below does use mutable variables n and x but note that all mutations are localized to the repeat function – no state changes (mutations) are visible from outside of the function

// repeat :: Int -> (a -> a) -> (a -> a)
const repeat = n => f => x =>
  {
    let m = 0, acc = x
    while (m < n)
      (m = m + 1, acc = f (acc))
    return acc
  }

// inc :: Int -> Int
const inc = x =>
  x + 1

console.log (repeat (1e8) (inc) (0))
// 100000000

This is going to have a lot of you saying "but that's not functional !" – I know, just relax. We can implement a Clojure-style loop/recur interface for constant-space looping using pure expressions; none of that while stuff.

Here we abstract while away with our loop function – it looks for a special recur type to keep the loop running. When a non-recur type is encountered, the loop is finished and the result of the computation is returned

const recur = (...args) =>
  ({ type: recur, args })
  
const loop = f =>
  {
    let acc = f ()
    while (acc.type === recur)
      acc = f (...acc.args)
    return acc
  }

const repeat = $n => f => x =>
  loop ((n = $n, acc = x) =>
    n === 0
      ? acc
      : recur (n - 1, f (acc)))
      
const inc = x =>
  x + 1

const fibonacci = $n =>
  loop ((n = $n, a = 0, b = 1) =>
    n === 0
      ? a
      : recur (n - 1, b, a + b))
      
console.log (repeat (1e7) (inc) (0)) // 10000000
console.log (fibonacci (100))        // 354224848179262000000
Thank you
  • 107,507
  • 28
  • 191
  • 224
  • 33
    Seems overcomplicated (I'm especially confused with `g => g(g)(x)`). Is there a benefit from a higher-order function over a first-order one, like in my solution? – Pavlo May 26 '15 at 08:18
  • Additionally, it doesn't seem to be a way of using index in the callback. – Pavlo May 26 '15 at 08:34
  • Pavlo, that's absolutely possible, but wasn't listed as a requirement. I've updated my answer. – Thank you May 26 '15 at 15:11
  • Can the techniques you use above be considered some kind of 'currying'? – Pineda Nov 22 '16 at 11:09
  • @Pineda yep ! All implementations above are defined in curried style – Thank you Nov 22 '16 at 17:22
  • 1
    @naomik: thanks for taking the time to post a link. much appreciated. – Pineda Nov 22 '16 at 19:58
  • this answer made me aware of the existence of the comma operator, imho this could be made a little bit longer without the usage of it and clearer for the newbies, or at least a comment would be nice, anyway this was a wonderful answer, thanks! – Alfonso Pérez Apr 21 '17 at 18:10
  • 1
    @AlfonsoPérez I appreciate the remark. I'll see if I can work a little hint in there somewhere ^_^ – Thank you Apr 21 '17 at 18:18
  • 1
    @naomik Farewell [TCO](https://stackoverflow.com/a/47207534/6445533)! I am devastated. –  Nov 10 '17 at 19:13
  • @ftor I’ve worked around it so much now it’s not a big deal to me anymore - JavaScript is under influence from so many different camps, there’s no way it can grow effectively in a way that makes everyone happy. I’ve been looking at Elm and ClojureScript (and now ReasonML) as replacements for JavaScript in my actual projects. Really enjoying Elm so far. – Thank you Nov 10 '17 at 19:53
  • i love you <3 this was so incredibly helpful and i think you've opened my eyes to another way of thinking about javascript! <3 – daikonradish Oct 05 '18 at 00:55
  • well i love daikon radish! thanks for your comment :D – Thank you Oct 05 '18 at 15:09
  • 26
    It seems this answer is accepted and well-rated because it must have taken a lot of effort, but I don't think it's a good answer. The correct answer to the question is "no". It is helpful to list a workaround as you did, but right after that you state that there is a better way. Why don't you just put that answer and remove the worse one at the top? Why are you explaining comma operators? Why do you bring up Clojure? Why, in general, so many tangents for a question with a 2 character answer? Simple questions aren't just a platform for users to do a presentation on some neat programming facts. – Sasha Kondrashov Feb 16 '19 at 08:23
  • 3
    @Timofey This answer is the compilation of several edits over the course of 2 years. I agree that this answer does need some final editing, but your edits removed too much. I'll revisit it soon, taking your comment and edit suggestions into sincere consideration. – Thank you Feb 16 '19 at 14:33
  • A very useful course in functional programming. But the short answer is `Array(N).keys()` as in `for (let i of Array(3).keys()){console.log(i)}` – Eran W Sep 16 '20 at 09:58
39
for (let i of Array(100).keys()) {
    console.log(i)
}
zerkms
  • 230,357
  • 57
  • 408
  • 498
  • This works, so that's great! But is a bit ugly in the sense that extra work is needed and this isn't what `Array` keys are used for. – at. May 26 '15 at 07:53
  • @at. indeed. But I'm not sure there is a haskell's synonym for `[0..x]` in JS more concise than in my answer. – zerkms May 26 '15 at 07:55
  • you might be correct that there's nothing more concise than this. – at. May 26 '15 at 07:57
  • OK, I understand why this works given the differences between `Array.prototype.keys` and `Object.prototype.keys`, but it sure is confusing at first glance. – Mark Reed Jan 27 '16 at 13:42
  • I would assume this to be faster than the top answer and less prone to max recursion overflow errors. – cchamberlain Aug 04 '16 at 20:14
  • 1
    @cchamberlain with TCO in ES2015 (not implemented anywhere though?) it might be the less of concern, but indeed :-) – zerkms Aug 04 '16 at 21:01
  • Why would you allocate an array just to throw it away? – Kugel Nov 30 '17 at 07:28
  • @Kugel because an OP made up an artificial problem that requires that? – zerkms Nov 30 '17 at 07:56
  • This will allocate space for the array. This is so close to a regular for loop that it doesn't make sense to use this approach. – X_Trust May 23 '19 at 15:52
  • @X_Trust the question is no about optimisation, it's about learning new language features. – zerkms May 23 '19 at 21:13
35

I think the best solution is to use let:

for (let i=0; i<100; i++) …

That will create a new (mutable) i variable for each body evaluation and assures that the i is only changed in the increment expression in that loop syntax, not from anywhere else.

I could kind of cheat and make my own generator. At least i++ is out of sight :)

That should be enough, imo. Even in pure languages, all operations (or at least, their interpreters) are built from primitives that use mutation. As long as it is properly scoped, I cannot see what is wrong with that.

You should be fine with

function* times(n) {
  for (let i = 0; i < n; i++)
    yield i;
}
for (const i of times(5)) {
  console.log(i);
}

But I don't want to use the ++ operator or have any mutable variables at all.

Then your only choice is to use recursion. You can define that generator function without a mutable i as well:

function* range(i, n) {
  if (i >= n) return;
  yield i;
  return yield* range(i+1, n);
}
times = (n) => range(0, n);

But that seems overkill to me and might have performance problems (as tail call elimination is not available for return yield*).

Matias
  • 327
  • 3
  • 10
Bergi
  • 513,640
  • 108
  • 821
  • 1,164
17

Here is another good alternative:

Array.from({ length: 3}).map(...);

Preferably, as @Dave Morse pointed out in the comments, you can also get rid of the map call, by using the second parameter of the Array.from function like so:

Array.from({ length: 3 }, () => (...))
oemera
  • 1,862
  • 10
  • 22
  • 1
    `Array.from` at MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from – Purplejacket May 11 '20 at 04:18
  • 4
    This should be the accepted answer! One small suggestion - you already get the map-like functionality you need for free with Array.from: `Array.from({ length: label.length }, (_, i) => (...))` This saves creating an empty temporary array just to kick off a call to map. – Dave Morse Jun 11 '20 at 03:49
15
const times = 4;
new Array(times).fill().map(() => console.log('test'));

This snippet will console.log test 4 times.

Hossam Mourad
  • 2,784
  • 4
  • 19
  • 18
14

I think it is pretty simple:

[...Array(3).keys()]

or

Array(3).fill()
Gergely Fehérvári
  • 7,405
  • 6
  • 43
  • 71
12

Answer: 09 December 2015

Personally, I found the accepted answer both concise (good) and terse (bad). Appreciate this statement might be subjective, so please read this answer and see if you agree or disagree

The example given in the question was something like Ruby's:

x.times do |i|
  do_stuff(i)
end

Expressing this in JS using below would permit:

times(x)(doStuff(i));

Here is the code:

let times = (n) => {
  return (f) => {
    Array(n).fill().map((_, i) => f(i));
  };
};

That's it!

Simple example usage:

let cheer = () => console.log('Hip hip hooray!');

times(3)(cheer);

//Hip hip hooray!
//Hip hip hooray!
//Hip hip hooray!

Alternatively, following the examples of the accepted answer:

let doStuff = (i) => console.log(i, ' hi'),
  once = times(1),
  twice = times(2),
  thrice = times(3);

once(doStuff);
//0 ' hi'

twice(doStuff);
//0 ' hi'
//1 ' hi'

thrice(doStuff);
//0 ' hi'
//1 ' hi'
//2 ' hi'

Side note - Defining a range function

A similar / related question, that uses fundamentally very similar code constructs, might be is there a convenient Range function in (core) JavaScript, something similar to underscore's range function.

Create an array with n numbers, starting from x

Underscore

_.range(x, x + n)

ES2015

Couple of alternatives:

Array(n).fill().map((_, i) => x + i)

Array.from(Array(n), (_, i) => x + i)

Demo using n = 10, x = 1:

> Array(10).fill().map((_, i) => i + 1)
// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]

> Array.from(Array(10), (_, i) => i + 1)
// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]

In a quick test I ran, with each of the above running a million times each using our solution and doStuff function, the former approach (Array(n).fill()) proved slightly faster.

arcseldon
  • 29,753
  • 14
  • 104
  • 117
10

I am late to the party, but since this question turns up often in search results, I would just like to add a solution that I consider to be the best in terms of readability while not being long (which is ideal for any codebase IMO). It mutates, but I'd make that tradeoff for KISS principles.

let times = 5
while( times-- )
    console.log(times)
// logs 4, 3, 2, 1, 0
J Garcia
  • 529
  • 4
  • 15
  • 4
    Thank you for being the voice of reason in what I can only describe as a higher-order lambda fetish party. I too ended up on this Q&A following an innocuous first-hit-on-Google-path and swiftly had my sanity desecrated by most of the answers here. Yours is the first one in the list that I would consider a straightforward solution to a straightforward problem. – Martin Devillers Apr 12 '20 at 20:50
  • Only issue with this is it's a little counterintuitive if you want to use the `times` variable inside the loop. Perhaps `countdown` would be a better naming. Otherwise, cleanest and clearest answer on the page. – Tony Brasunas Jul 20 '20 at 22:56
9
Array(100).fill().map((_,i)=> console.log(i) );

This version satisifies the OP's requirement for immutability. Also consider using reduce instead of map depending on your use case.

This is also an option if you don't mind a little mutation in your prototype.

Number.prototype.times = function(f) {
   return Array(this.valueOf()).fill().map((_,i)=>f(i));
};

Now we can do this

((3).times(i=>console.log(i)));

+1 to arcseldon for the .fill suggestion.

Tom
  • 7,474
  • 7
  • 40
  • 56
  • Voted down, as [fill method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill) is not supported in IE or Opera or PhantomJS – morhook Jul 13 '16 at 15:59
7

Not something I would teach (or ever use in my code), but here's a codegolf-worthy solution without mutating a variable, no need for ES6:

Array.apply(null, {length: 10}).forEach(function(_, i){
    doStuff(i);
})

More of an interesting proof-of-concept thing than a useful answer, really.

doldt
  • 4,312
  • 3
  • 19
  • 36
  • Coudn't `Array.apply(null, {length: 10})` be just `Array(10)`? – Pavlo May 26 '15 at 07:46
  • 1
    @Pavlo, actually, no. Array(10) would create an array of length 10, but without any keys defined in it, which makes the forEach construct not usable in this case. But indeed it can be simplified if you dont use forEach, see zerkms 's answer (that uses ES6 though!). – doldt May 26 '15 at 07:49
  • creative @doldt, but I'm looking for something teachable and simple. – at. May 26 '15 at 07:54
4

If you're willing to use a library, there's also lodash _.times or underscore _.times:

_.times(x, i => {
   return doStuff(i)
})

Note this returns an array of the results, so it's really more like this ruby:

x.times.map { |i|
  doStuff(i)
}
ronen
  • 2,169
  • 24
  • 22
3

Afaik, there is no mechanism in ES6 similar to Ruby's times method. But you can avoid mutation by using recursion:

let times = (i, cb, l = i) => {
  if (i === 0) return;

  cb(l - i);
  times(i - 1, cb, l);
}

times(5, i => doStuff(i));

Demo: http://jsbin.com/koyecovano/1/edit?js,console

Pavlo
  • 35,298
  • 12
  • 72
  • 105
  • I like this approach, I love recursion. But I'd love something simpler to show new JavaScript users loops. – at. May 26 '15 at 07:47
3

In the functional paradigm repeat is usually an infinite recursive function. To use it we need either lazy evaluation or continuation passing style.

Lazy evaluated function repetition

const repeat = f => x => [x, () => repeat(f) (f(x))];
const take = n => ([x, f]) => n === 0 ? x : take(n - 1) (f());

console.log(
  take(8) (repeat(x => x * 2) (1)) // 256
);

I use a thunk (a function without arguments) to achieve lazy evaluation in Javascript.

Function repetition with continuation passing style

const repeat = f => x => [x, k => k(repeat(f) (f(x)))];
const take = n => ([x, k]) => n === 0 ? x : k(take(n - 1));

console.log(
  take(8) (repeat(x => x * 2) (1)) // 256
);

CPS is a little scary at first. However, it always follows the same pattern: The last argument is the continuation (a function), which invokes its own body: k => k(...). Please note that CPS turns the application inside out, i.e. take(8) (repeat...) becomes k(take(8)) (...) where k is the partially applied repeat.

Conclusion

By separating the repetition (repeat) from the termination condition (take) we gain flexibility - separation of concerns up to its bitter end :D

1

Advantages of this solution

  • Simplest to read / use (imo)
  • Return value can be used as a sum, or just ignored
  • Plain es6 version, also link to TypeScript version of the code

Disadvantages - Mutation. Being internal only I don't care, maybe some others will not either.

Examples and Code

times(5, 3)                       // 15    (3+3+3+3+3)

times(5, (i) => Math.pow(2,i) )   // 31    (1+2+4+8+16)

times(5, '<br/>')                 // <br/><br/><br/><br/><br/>

times(3, (i, count) => {          // name[0], name[1], name[2]
    let n = 'name[' + i + ']'
    if (i < count-1)
        n += ', '
    return n
})

function times(count, callbackOrScalar) {
    let type = typeof callbackOrScalar
    let sum
    if (type === 'number') sum = 0
    else if (type === 'string') sum = ''

    for (let j = 0; j < count; j++) {
        if (type === 'function') {
            const callback = callbackOrScalar
            const result = callback(j, count)
            if (typeof result === 'number' || typeof result === 'string')
                sum = sum === undefined ? result : sum + result
        }
        else if (type === 'number' || type === 'string') {
            const scalar = callbackOrScalar
            sum = sum === undefined ? scalar : sum + scalar
        }
    }
    return sum
}

TypeScipt version
https://codepen.io/whitneyland/pen/aVjaaE?editors=0011

whitneyland
  • 9,968
  • 8
  • 55
  • 65
1

The simplest way I can think of for creating list/array within range

Array.from(Array(max-min+1), (_, index) => index+min)

mestea
  • 11
  • 1
0

addressing the functional aspect:

function times(n, f) {
    var _f = function (f) {
        var i;
        for (i = 0; i < n; i++) {
            f(i);
        }
    };
    return typeof f === 'function' && _f(f) || _f;
}
times(6)(function (v) {
    console.log('in parts: ' + v);
});
times(6, function (v) {
    console.log('complete: ' + v);
});
Nina Scholz
  • 323,592
  • 20
  • 270
  • 324
  • 5
    "addressing the functional aspect" and then using imperative loop with a mutable `i`. What is the reason to even use `times` over plain old `for` then? – zerkms May 26 '15 at 11:22
  • reuse like `var twice = times(2);`. – Nina Scholz May 26 '15 at 12:04
  • So why not just use `for` twice? – zerkms May 26 '15 at 19:01
  • i am not afraid to use for. the question was something not to use a variabele. but the result is always some kind of caching aka variable. – Nina Scholz May 26 '15 at 20:08
  • 1
    "was something not to use a variabele" --- and you still use it - `i++`. It's not obvious how wrapping something unacceptable in a function makes it better. – zerkms May 26 '15 at 21:35
0

Generators? Recursion? Why so much hatin' on mutatin'? ;-)

If it is acceptable as long as we "hide" it, then just accept the use of a unary operator and we can keep things simple:

Number.prototype.times = function(f) { let n=0 ; while(this.valueOf() > n) f(n++) }

Just like in ruby:

> (3).times(console.log)
0
1
2
conny
  • 9,012
  • 6
  • 35
  • 43
  • 2
    Thumbs up: "Why so much hatin' on mutatin'?" – Sarreph Mar 20 '17 at 19:38
  • 1
    Thumbs up for simplicity, thumbs down for going ruby-style a bit too much with the monkeypatch. Just say no to those bad bad monkeys. – mrm May 02 '17 at 15:29
  • 1
    @mrm is this "monkey patching", isn't this just a case of extension? Embrace & extend :) – conny May 31 '17 at 07:07
  • No. Adding functions to Number (or String or Array or any other class that you didn't author) are, by definition, either polyfills or monkey patches--and even polyfills are not recommended. Read the definitions of "monkey patch", "polyfill," and a recommended alternative, "ponyfill." That's what you want. – mrm May 31 '17 at 15:15
  • To extend Number you would do: class SuperNumber extends Number { times(fn) { for (let i = 0; i < this; i ++) { fn(i); } } } – Alexander Sep 10 '17 at 13:47
0

I wrapped @Tieme s answer with a helper function.

In TypeScript:

export const mapN = <T = any[]>(count: number, fn: (...args: any[]) => T): T[] => [...Array(count)].map((_, i) => fn())

Now you can run:

const arr: string[] = mapN(3, () => 'something')
// returns ['something', 'something', 'something']
Andries
  • 915
  • 7
  • 11
0

I made this:

function repeat(func, times) {
    for (var i=0; i<times; i++) {
        func(i);
    }
}

Usage:

repeat(function(i) {
    console.log("Hello, World! - "+i);
}, 5)

/*
Returns:
Hello, World! - 0
Hello, World! - 1
Hello, World! - 2
Hello, World! - 3
Hello, World! - 4
*/

The i variable returns the amount of times it has looped - useful if you need to preload an x amount of images.

Nanoo
  • 562
  • 4
  • 14
0

I am just going to put this here. If you are looking for a compact function without using Arrays and you have no issue with mutability/immutability :

var g =x=>{/*your code goes here*/x-1>0?g(x-1):null};
 
Vibhu
  • 496
  • 1
  • 3
  • 11
0

For me, this is the easiest answer to understand for many levels of developers

const times = (n, callback) => {
    while (n) {
        callback();
        n--;
    }
}

times(10, ()=> console.log('hello'))
found8
  • 19
  • 4