One interesting approach is to realize that we want to do a fold (something like Array.prototype.reduce) but one which we can escape early. The answer from zer00ne does this by choosing to check the condition on every iteration, and just continually returning the accumulator each time the condition is not met. That's fine for many use-cases, but it would be nice to make it more explicit.
I know of two ways to do this. One is to have some signal value that we would return -- probably a symbol -- to say, "We're done, just return the accumulator." The downside is that this function now depends on that external signal value. It's not terrible, and often might be the right solution. It's not hard to write, and I'll leave it as an exercise.
The other technique is to require the callback to explicitly choose whether to continue the iteration or stop by supplying it with next and done functions. If we want to continue, we call next with the next accumulator value. If we're finished, we just call done. Here's a version of that:
const fold = (fn) => (a) => ([x, ...xs]) =>
x == undefined ? a : fn (a, x, (r) => fold (fn) (r) (xs), () => a)
const sumUpTo = (n) =>
fold ((a, x, next, done) => a + x > n ? done () : next (a + x)) (0)
console .log (sumUpTo (10) ([1, 2, 3, 5, 7, 9])) //=> 6
console .log (sumUpTo (4) ([1, 2, 3, 5, 7, 9])) //=> 3
console .log (sumUpTo (10) ([1, 2, 3, 4, 5, 6])) //=> 10
sumUpTo takes our total value and returns a function that takes a list of numbers. It does this by calling fold using a callback function, the initial value (0 for a sum), and eventually passing our list of numbers. That callback does the work we care about. Then fold repeatedly calls it until it runs out of values or done is called.
We can break down the one-liner version above to focus specifically on the callback, if it makes it more clear:
const callback = (n) => (a, x, next, done) =>
a + x > n
? done ()
: next (a + x)
const sumUpTo = (n) => fold (callback (n)) (0)
It's a very elegant pattern, to my mind.