157

How can I break the iteration of reduce() method?

for:

for (var i = Things.length - 1; i >= 0; i--) {
  if(Things[i] <= 0){
    break;
  }
};

reduce()

Things.reduce(function(memo, current){
  if(current <= 0){
    //break ???
    //return; <-- this will return undefined to memo, which is not what I want
  }
}, 0)
Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
Julio Marins
  • 10,039
  • 8
  • 48
  • 54
  • 1
    What is `current` in the code above? I don't see how these can do the same thing. In any case there are methods that break early like `some`, `every`, `find` – elclanrs Mar 22 '16 at 01:00
  • `some` and `every` return booleans and `find` return a single record, what I want is to run operations to generate a memo. `current` is the currentValue. [reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) – Julio Marins Mar 22 '16 at 01:03
  • I mean what is `current` in the first piece of code? – elclanrs Mar 22 '16 at 01:09
  • 3
    The answer is you cannot break early from `reduce` , you'll have to find another way with builtin functions that exit early or create your own helper, or use lodash or something. Can you post a full example of what you want to do? – elclanrs Mar 22 '16 at 01:11
  • nothing special @elclanrs, just out of curiosity if it was possible to do that knowing that reduce is a case of iteration. – Julio Marins Mar 22 '16 at 01:14
  • I think it's a bad idea to do it. From the MDN definition of the `reduce` function: `The reduce() method applies a function against an accumulator and each value of the array (from left-to-right) to reduce it to a single value`. If you need something different than this, then you probably want to use a simple `for` loop. – XCS Mar 22 '16 at 01:33
  • @Cristy—"*…from left to right…*" doesn't seem right, MDN needs fixing. *reduce* goes from lowest to highest index, which might be right to left, or top to bottom, or bottom to top, depending on your point of view. Of course there is *reduceRight*, but it's been influenced by more recent culture (*c.f. pop/push, shift/unshift* which don't infer any endian–ness). – RobG Mar 22 '16 at 02:51
  • @RobG You are right, but it says so because `Arrays` are instantiated like this: `arr = [0, 1, 2, 3]`, where index `0` is at the left and the right most index is at the right. I don't see how you could look at this and not notice how `0` is left and `3` is right :) – XCS Mar 22 '16 at 14:07
  • @Cristy—sure, but array elements may be added in any sequence and may be moved around. Also, Arrays are just objects, so their properties only have a concept of order because of their index values, e.g. *for..in* **might** return the values in any order (IE used to return them in the order they were added). ;-) – RobG Mar 22 '16 at 23:47
  • In JS with higher order functions, one should avoid having such sinful thoughts at all costs. JS doesn't have a data type to represent a value alongside a failure possibility such as `Maybe Int` which may return ie `Just 10` or `Nothing`, or such as `Either String Int` which may return `Left "Boom..!"` or `Right 10`. Short circuiting of higher order functions could only then be implemented properly if while folding you get a `Nothing` or `Left "quick result"`. This is available as `foldM` (monadic fold) in true functional languages like Haskell. – Redu May 09 '19 at 15:58
  • It's very simple. You don't break. You just stop mutating the state (i.e keep returning the accumulator that you're happy with). – Chris Harrison Aug 22 '20 at 16:16

17 Answers17

156

You CAN break on any iteration of a .reduce() invocation by mutating the 4th argument of the reduce function: "array". No need for a custom reduce function. See Docs for full list of .reduce() parameters.

Array.prototype.reduce((acc, curr, i, array))

The 4th argument is the array being iterated over.

const array = ['apple', '-pen', '-pineapple', '-pen'];
const x = array
.reduce((acc, curr, i, arr) => {
    if(i === 2) arr.splice(1);  // eject early
    return acc += curr;
  }, '');
console.log('x: ', x);  // x:  apple-pen-pineapple

WHY?:

The one and only reason I can think of to use this instead of the many other solutions presented is if you want to maintain a functional programming methodology to your algorithm, and you want the most declarative approach possible to accomplish that. If your entire goal is to literally REDUCE an array to an alternate non-falsey primitive (string, number, boolean, Symbol) then I would argue this IS in fact, the best approach.

WHY NOT?

There's a whole list of arguments to make for NOT mutating function parameters as it's a bad practice.


UPDATE

Some of the commentators make a good point that the original array is being mutated in order to break early inside the .reduce() logic.

Therefore, I've modified the answer slightly by adding a .slice(0) before calling a follow-on .reduce() step, yielding a copy of the original array. NOTE: Similar ops that accomplish the same task are slice() (less explicit), and spread operator [...array] (slightly less performant). Bear in mind, all of these add an additional constant factor of linear time to the overall runtime ... + O(n).

The copy, serves to preserve the original array from the eventual mutation that causes ejection from iteration.

const array = ['apple', '-pen', '-pineapple', '-pen'];
const x = array
    .slice(0)                         // create copy of "array" for iterating
    .reduce((acc, curr, i, arr) => {
       if (i === 2) arr.splice(1);    // eject early by mutating iterated copy
       return (acc += curr);
    }, '');

console.log("x: ", x, "\noriginal Arr: ", array);
// x:  apple-pen-pineapple
// original Arr:  ['apple', '-pen', '-pineapple', '-pen']
Tobiah Rex
  • 2,247
  • 2
  • 14
  • 17
  • 6
    This is really BAD ADVICE, because `splice` performs a visible mutation (`array`). According to the functional paradigm you'd use either a reduce in continuation passing style or utilize lazy evaluation with a right-associative reduce. Or, as a simpler alternative, just plain recursion. –  Apr 16 '18 at 13:41
  • **Hold On!** *by mutating the 4th argument of the reduce function: "array"* is not a correct statement. In this case it is happening (the example in the answer) because its cutting the array to single length array (first element) while its already reached index **2**, obviously next time, for index **3** it will not get a item to iterate (as you are mutating the original reference to array of length **1**). In case you perform a pop that will also mutate the source array too but not stop in between (if you are not at the second last index). – Koushik Chatterjee Sep 01 '18 at 16:04
  • @KoushikChatterjee My statement is correct for my implicit meaning. It is not correct for your explicit meaning. You should offer a suggestion on modifying the statement to include your points and I'll make the edit as it would improve the overall answer. – Tobiah Rex Sep 05 '18 at 03:28
  • This is BAD! Like many others have said, it will mutate your source object. If you're application is built on immutability which pretty much all are these days then you're opening yourself up to disaster. – TheNickyYo Sep 05 '18 at 13:42
  • @TheNickyYo Hence the part titled "Why Not?". – Tobiah Rex Sep 07 '18 at 03:15
  • 2
    I prefer reaching for the spread operator to avoid any unwanted mutations, [...array].reduce() – eballeste Jun 15 '19 at 19:59
  • @eballeste Yup, totally works. Interesting benchmarks [here](https://stackoverflow.com/a/54793076/6406690) comparing the two. – Tobiah Rex Jan 17 '20 at 21:30
  • Yep @eballeste, and that one simple step makes this solution fantastic – gdibble Jan 31 '22 at 07:34
  • It would be unwise to use mutation to short circuit a reduction. Write a custom reduce function which allows for early breaking. Don't shoehorn the native into doing something it wasn't designed for. – Mario May 24 '22 at 17:26
  • @Mario I would argue using a custom reduce function is a shoehorn. A shoehorn is an outside instrument used to assist you putting on your shoes. A short-circuit is utilizing no outside instrument to assist you, but rather working creatively within the confines of the functional arguments you're natively given although often unused/ignored (4th arg; array). Using your example, this solution is more appropriately compared to pulling off one shoe using the toe of the partner shoe; something some parents frown upon but is quite expeditious and effective in ejecting my foot from my shoes. – Tobiah Rex May 24 '22 at 18:38
  • @TobiahRex - I would never write a function which passes an object whose mutation (side effect!) produces a desired result. The design of the function makes me cringe. I'll use a better function even if I have to write it. – Mario May 25 '22 at 21:36
  • that should be this, if you want break in this loop `arr.reduce((acc, item, index, arr) => { if (condition) acc += 1 else arr.splice(index) return acc })` – flyflydogdog May 26 '22 at 08:27
42

Don't use reduce. Just iterate on the array with normal iterators (for, etc) and break out when your condition is met.

Johann
  • 27,536
  • 39
  • 165
  • 279
  • 3
    Yes, this answer could be improved by explaining WHY it's possibly not best practice to use functional programming if you need to break the loop. One would assume the OP is fully aware of basic iterators and maybe they just want to avoid polluting the scope, who knows. – Phil Tune Feb 25 '21 at 14:54
  • 1
    (I encountered this answer during review) I'd argue that this answer have value and should be kept. Although the OP could be aware what he is doing in wanting to use `reduce`, others could find it helpful in searching for a solution to their problem (as indicated by the number of upvotes). – Shaido Feb 26 '21 at 06:20
21

You can use functions like some and every as long as you don't care about the return value. every breaks when the callback returns false, some when it returns true:

things.every(function(v, i, o) {
  // do stuff 
  if (timeToBreak) {
    return false;
  } else {
    return true;
  }
}, thisArg);

Edit

A couple of comments that "this doesn't do what reduce does", which is true, but it can. Here's an example of using every in a similar manner to reduce that returns as soon as the break condition is reached.

// Soruce data
let data = [0,1,2,3,4,5,6,7,8];

// Multiple values up to 5 by 6, 
// create a new array and stop processing once 
// 5 is reached

let result = [];

data.every(a => a < 5? result.push(a*6) : false);

console.log(result);

This works because the return value from push is the length of the result array after the new element has been pushed, which will always be 1 or greater (hence true), otherwise it returns false and the loop stops.

RobG
  • 142,382
  • 31
  • 172
  • 209
  • 50
    But if he is trying to do `reduce` then by definition he **does** care about the return value. –  Mar 22 '16 at 03:36
  • 1
    @torazaburo—sure, but I don't see it being used in the OP and there are other ways of getting a result. ;-) – RobG Mar 22 '16 at 08:47
  • `const isKnownZone = KNOWN_ZONES.some((v) => curZone.substr(v.length) === v)` I could use reduce, but that wouldn't be as efficient. The way I'm thinking about it is some and every are boolean functions... some elements are true, every element is true, in the set – Ray Foss Feb 04 '21 at 21:33
  • the purpose of using `reduce` 99& cant be met by `every` – fedeghe Feb 23 '22 at 13:28
  • is there a way to accomplish this with an async reducer function? – Abdel P. Sep 27 '22 at 00:03
10

There is no way, of course, to get the built-in version of reduce to exit prematurely.

But you can write your own version of reduce which uses a special token to identify when the loop should be broken.

var EXIT_REDUCE = {};

function reduce(a, f, result) {
  for (let i = 0; i < a.length; i++) {
    let val = f(result, a[i], i, a);
    if (val === EXIT_REDUCE) break;
    result = val;
  }
  return result;
}

Use it like this, to sum an array but exit when you hit 99:

reduce([1, 2, 99, 3], (a, b) => b === 99 ? EXIT_REDUCE : a + b, 0);

> 3
  • 1
    You can use [lazy evaluation or CPS](http://stackoverflow.com/q/34976936/5536315) to achieve the desired behavior: –  Mar 23 '16 at 07:36
  • The first sentence of this answer is incorrect. You can break, see my answer below for details. – Tobiah Rex Jan 25 '18 at 19:33
6

Array.every can provide a very natural mechanism for breaking out of high order iteration.

const product = function(array) {
    let accumulator = 1;
    array.every( factor => {
        accumulator *= factor;
        return !!factor;
    });
    return accumulator;
}
console.log(product([2,2,2,0,2,2]));
// 0
Doug Coburn
  • 2,485
  • 27
  • 24
1

You can break every code - and thus every build in iterator - by throwing an exception:

function breakReduceException(value) {
    this.value = value
}

try {
    Things.reduce(function(memo, current) {
        ...
        if (current <= 0) throw new breakReduceException(memo)
        ...
    }, 0)
} catch (e) {
    if (e instanceof breakReduceException) var memo = e.value
    else throw e
}
Koudela
  • 181
  • 6
  • 7
    This is probably the least efficient execution-wise of all the answers. Try/catch breaks the existing execution context and falls back to the 'slow path' of execution. Say goodbye to any optimizations that V8 does under the covers. – Evan Plaice Feb 10 '18 at 04:17
  • 8
    Not extreme enough. How about this: `if (current <= 0) window.top.close()` – user56reinstatemonica8 Aug 13 '19 at 10:29
1

You can use try...catch to exit the loop.

try {
  Things.reduce(function(memo, current){
    if(current <= 0){
      throw 'exit loop'
      //break ???
      //return; <-- this will return undefined to memo, which is not what I want
    }
  }, 0)
} catch {
  // handle logic
}
Bryant
  • 73
  • 7
0

You cannot break from inside of a reduce method. Depending on what you are trying to accomplish you could alter the final result (which is one reason you may want to do this)

const result = [1, 1, 1].reduce((a, b) => a + b, 0); // returns 3

console.log(result);

const result = [1, 1, 1].reduce((a, b, c, d) => {
  if (c === 1 && b < 3) {
    return a + b + 1;
  } 
  return a + b;
}, 0); // now returns 4

console.log(result);

Keep in mind: you cannot reassign the array parameter directly

const result = [1, 1, 1].reduce( (a, b, c, d) => {
  if (c === 0) {
    d = [1, 1, 2];
  } 
  return a + b;
}, 0); // still returns 3

console.log(result);

However (as pointed out below), you CAN affect the outcome by changing the array's contents:

const result = [1, 1, 1].reduce( (a, b, c, d) => {
  if (c === 0) {
    d[2] = 100;
  } 
  return a + b;
}, 0); // now returns 102

console.log(result);
VLAZ
  • 26,331
  • 9
  • 49
  • 67
Erik Waters
  • 177
  • 1
  • 13
  • 1
    Re "*You cannot mutate the argument values directly in a way that affects subsequent computations*", that isn't true. ECMA-262 says: [*If existing elements of the array are changed, their value as passed to callbackfn will be the value at the time reduce visits them*](http://ecma-international.org/ecma-262/7.0/index.html#sec-array.prototype.reduce). Your example doesn't work because you're assigning a new value to *d*, not modifying the original array. Replace `d = [1, 1, 2]` with `d[2] = 6` and see what happens. ;-) – RobG Feb 03 '17 at 21:37
0

As the promises have resolve and reject callback arguments, I created the reduce workaround function with the break callback argument. It takes all the same arguments as native reduce method, except the first one is an array to work on (avoid monkey patching). The third [2] initialValue argument is optional. See the snippet below for the function reducer.

var list = ["w","o","r","l","d"," ","p","i","e","r","o","g","i"];

var result = reducer(list,(total,current,index,arr,stop)=>{
  if(current === " ") stop(); //when called, the loop breaks
  return total + current;
},'hello ');

console.log(result); //hello world

function reducer(arr, callback, initial) {
  var hasInitial = arguments.length >= 3;
  var total = hasInitial ? initial : arr[0];
  var breakNow = false;
  for (var i = hasInitial ? 0 : 1; i < arr.length; i++) {
    var currentValue = arr[i];
    var currentIndex = i;
    var newTotal = callback(total, currentValue, currentIndex, arr, () => breakNow = true);
    if (breakNow) break;
    total = newTotal;
  }
  return total;
}

And here is the reducer as an Array method modified script:

Array.prototype.reducer = function(callback,initial){
  var hasInitial = arguments.length >= 2;
  var total = hasInitial ? initial : this[0];
  var breakNow = false;
  for (var i = hasInitial ? 0 : 1; i < this.length; i++) {
    var currentValue = this[i];
    var currentIndex = i;
    var newTotal = callback(total, currentValue, currentIndex, this, () => breakNow = true);
    if (breakNow) break;
    total = newTotal;
  }
  return total;
};

var list = ["w","o","r","l","d"," ","p","i","e","r","o","g","i"];

var result = list.reducer((total,current,index,arr,stop)=>{
  if(current === " ") stop(); //when called, the loop breaks
  return total + current;
},'hello ');


console.log(result);
Paweł
  • 4,238
  • 4
  • 21
  • 40
0

Reduce functional version with break can be implemented as 'transform', ex. in underscore.

I tried to implement it with a config flag to stop it so that the implementation reduce doesn't have to change the data structure that you are currently using.

const transform = (arr, reduce, init, config = {}) => {
  const result = arr.reduce((acc, item, i, arr) => {
    if (acc.found) return acc

    acc.value = reduce(config, acc.value, item, i, arr)

    if (config.stop) {
      acc.found = true
    }

    return acc
  }, { value: init, found: false })

  return result.value
}

module.exports = transform

Usage1, simple one

const a = [0, 1, 1, 3, 1]

console.log(transform(a, (config, acc, v) => {
  if (v === 3) { config.stop = true }
  if (v === 1) return ++acc
  return acc
}, 0))

Usage2, use config as internal variable

const pixes = Array(size).fill(0)
const pixProcessed = pixes.map((_, pixId) => {
  return transform(pics, (config, _, pic) => {
    if (pic[pixId] !== '2') config.stop = true 
    return pic[pixId]
  }, '0')
})

Usage3, capture config as external variable

const thrusts2 = permute([9, 8, 7, 6, 5]).map(signals => {
  const datas = new Array(5).fill(_data())
  const ps = new Array(5).fill(0)

  let thrust = 0, config
  do {

    config = {}
    thrust = transform(signals, (_config, acc, signal, i) => {
      const res = intcode(
        datas[i], signal,
        { once: true, i: ps[i], prev: acc }
      )

      if (res) {
        [ps[i], acc] = res 
      } else {
        _config.stop = true
      }

      return acc
    }, thrust, config)

  } while (!config.stop)

  return thrust
}, 0)
windmaomao
  • 7,120
  • 2
  • 32
  • 36
0

Providing you do not need to return an array, perhaps you could use some()?

Use some instead which auto-breaks when you want. Send it a this accumulator. Your test and accumulate function cannot be an arrow function as their this is set when the arrow function is created.

const array = ['a', 'b', 'c', 'd', 'e'];
var accum = {accum: ''};
function testerAndAccumulator(curr, i, arr){
    this.tot += arr[i];
    return curr==='c';
};
accum.tot = "";
array.some(testerAndAccumulator, accum);

var result = accum.tot;

In my opinion this is the better solution to the accepted answer provided you do not need to return an array (eg in a chain of array operators), as you do not alter the original array and you do not need to make a copy of it which could be bad for large arrays.

Rewind
  • 2,554
  • 3
  • 30
  • 56
0

So, to terminate even earlier the idiom to use would be arr.splice(0). Which prompts the question, why can't one just use arr = [] in this case? I tried it and the reduce ignored the assignment, continuing on unchanged. The reduce idiom appears to respond to forms such as splice but not forms such as the assignment operator??? - completely unintuitive - and has to be rote-learnt as precepts within the functional programming credo ...

const array = ['9', '91', '95', '96', '99'];
const x = array
.reduce((acc, curr, i, arr) => {
    if(i === 2) arr.splice(1);  // eject early
    return acc += curr;
  }, '');
console.log('x: ', x);  // x:  99195
luzaranza
  • 49
  • 4
  • 2
    With `arr.splice()` you are affecting the current array. With assignment, that array still exists in memory untampered, you've only changed the reference of the var to a completely new array. Reduce wont care about the reference change because it has it's own references to the untampered array (`acc` and `curr` in your example). – Randy Hall Sep 11 '21 at 18:57
  • @RandyHall `acc` and `curr` aren't references to the array - since `acc` is the return value, and `curr` is the current element. `arr` is a reference to the untampered array, though - of course internally it would be using `this` (or the engine's non-js equivalent) – somebody Feb 19 '22 at 21:15
0

The problem is, that inside of the accumulator it is not possible to just stop the whole process. So by design something in the outer scope must be manipulated, which always leads to a necessary mutation.

As many others already mentioned throw with try...catch is not really an approach which can be called "solution". It is more a hack with many unwanted side effects.

The only way to do this WITHOUT ANY MUTATIONS is by using a second compare function, which decides whether to continue or stop. To still avoid a for-loop, it has to be solved with a recursion.

The code:

function reduceCompare(arr, cb, cmp, init) {
    return (function _(acc, i) {
        return i < arr.length && cmp(acc, arr[i], i, arr) === true ? _(cb(acc, arr[i], i, arr), i + 1) : acc;
    })(typeof init !== 'undefined' ? init : arr[0], 0);
}

This can be used like:

var arr = ['a', 'b', 'c', 'd'];

function join(acc, curr) {
    return acc + curr;
}

console.log(
    reduceCompare(
        arr,
        join,
        function(acc) { return acc.length < 1; },
        ''
    )
); // logs 'a'

console.log(
    reduceCompare(
        arr,
        join,
        function(acc, curr) { return curr !== 'c'; },
        ''
    )
); // logs 'ab'

console.log(
    reduceCompare(
        arr,
        join,
        function(acc, curr, i) { return i < 3; },
        ''
    )
); // logs 'abc'

I made an npm library out of this, also containing a TypeScript and ES6 version. Feel free to use it:

https://www.npmjs.com/package/array-reduce-compare

or on GitHub:

https://github.com/StefanJelner/array-reduce-compare

Stefan Jelner
  • 111
  • 1
  • 4
0

You could to write your own reduce method. Invoking it like this, so it follows same logic and you control your own escape / break solution. It retains functional style and allows breaking.

const reduce = (arr, fn, accum) => {
  const len = arr.length;
  let result = null;
  for(let i = 0; i < len; i=i+1) {
    result = fn(accum, arr[i], i)
    if (accum.break === true) {
      break;
    }
  }
  return result
}

const arr = ['a', 'b', 'c', 'shouldnotgethere']
const myResult = reduce(arr, (accum, cur, ind) => {
  accum.result = accum.result + cur;
  if(ind === 2) {
    accum.break = true
  }
  return accum
}, {result:'', break: false}).result

console.log({myResult})

Or create your own reduce recursion method:

const rcReduce = (arr, accum = '', ind = 0) => {
  const cur = arr.shift();
  accum += cur;
  const isBreak = ind > 1
  return arr.length && !isBreak ? rcReduce(arr, accum, ind + 1) : accum
}

const myResult = rcReduce(['a', 'b', 'c', 'shouldngethere'])
console.log({myResult})
Steve Tomlin
  • 3,391
  • 3
  • 31
  • 63
-1

Another simple implementation that I came with solving the same issue:

function reduce(array, reducer, first) {
  let result = first || array.shift()

  while (array.length > 0) {
    result = reducer(result, array.shift())
    if (result && result.reduced) {
      return result.reduced
    }
  }

  return result
}
Aleksei
  • 145
  • 1
  • 8
-1

If you want to chain promises sequentially with reduce using the pattern below:

return [1,2,3,4].reduce(function(promise,n,i,arr){
   return promise.then(function(){
       // this code is executed when the reduce loop is terminated,
       // so truncating arr here or in the call below does not works
       return somethingReturningAPromise(n);
   });
}, Promise.resolve());

But need to break according to something happening inside or outside a promise things become a little bit more complicated because the reduce loop is terminated before the first promise is executed, making truncating the array in the promise callbacks useless, I ended up with this implementation:

function reduce(array, promise, fn, i) {
  i=i||0;
  return promise
  .then(function(){
    return fn(promise,array[i]);
  })
  .then(function(result){
    if (!promise.break && ++i<array.length) {
      return reduce(array,promise,fn,i);
    } else {
      return result;
    }
  })
}

Then you can do something like this:

var promise=Promise.resolve();
reduce([1,2,3,4],promise,function(promise,val){
  return iter(promise, val);
}).catch(console.error);

function iter(promise, val) {
  return new Promise(function(resolve, reject){
    setTimeout(function(){
      if (promise.break) return reject('break');
      console.log(val);
      if (val==3) {promise.break=true;}
      resolve(val);
    }, 4000-1000*val);
  });
}
luxigo
  • 123
  • 1
  • 5
-1

I solved it like follows, for example in the some method where short circuiting can save a lot:

const someShort = (list, fn) => {
  let t;
  try {
    return list.reduce((acc, el) => {
      t = fn(el);
      console.log('found ?', el, t)
      if (t) {
        throw ''
      }
      return t
    }, false)
  } catch (e) {
    return t
  }
}

const someEven = someShort([1, 2, 3, 1, 5], el => el % 2 === 0)

console.log(someEven)

UPDATE

Away more generic answer could be something like the following

const escReduce = (arr, fn, init, exitFn) => {
    try {
      return arr.reduce((...args) => {
          if (exitFn && exitFn(...args)) {
              throw args[0]
          }
          return fn(...args)
        }, init)
    } catch(e){ return e }
}

escReduce(
  Array.from({length: 100}, (_, i) => i+1),
  (acc, e, i) => acc * e,
    1,
    acc => acc > 1E9 
); // 6227020800

give we pass an optional exitFn which decides to break or not

fedeghe
  • 1,243
  • 13
  • 22