17

I am trying to implement a while loop using promises.

The method outlined here seems to work. http://blog.victorquinn.com/javascript-promise-while-loop it uses a function like this

var Promise = require('bluebird');

var promiseWhile = function(condition, action) {
    var resolver = Promise.defer();

    var loop = function() {
        if (!condition()) return resolver.resolve();
        return Promise.cast(action())
            .then(loop)
            .catch(resolver.reject);
    };

    process.nextTick(loop);

    return resolver.promise;
};

This seems to use anti-patterns and deprecated methods like cast and defer.

Does anyone know a better or more modern way to accomplish this?

Thanks

Yves M.
  • 29,855
  • 23
  • 108
  • 144
Brian Keith
  • 318
  • 3
  • 14
  • Can you explain in words what you're really trying to accomplish? Remember that a busy-wait loop is a horrible thing in Javascript. It makes a lot more sense to use some sort of event handler rather than repeatedly call something in a tight loop. And, what does `action()` do? Perhaps show an example of how you would use this? – jfriend00 Mar 31 '15 at 21:15

2 Answers2

20

cast can be translated to resolve. defer should indeed not be used.

You'd create your loop only by chaining and nesting then invocations onto an initial Promise.resolve(undefined).

function promiseWhile(predicate, action, value) {
    return Promise.resolve(value).then(predicate).then(function(condition) {
        if (condition)
            return promiseWhile(predicate, action, action());
    });
}

Here, both predicate and action may return promises. For similar implementations also have a look at Correct way to write loops for promise. Closer to your original function would be

function promiseWhile(predicate, action) {
    function loop() {
        if (!predicate()) return;
        return Promise.resolve(action()).then(loop);
    }
    return Promise.resolve().then(loop);
}
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • you can change the `loop` function to accept a value and then resolve it when the predicate is false, just to get the last value returned by the `action` function. – Sami Jun 30 '16 at 13:09
  • @Sami: Yes, [the answer in the linked question](http://stackoverflow.com/a/24660323/1048572) does that for example. However I wanted to keep this answer close to the OPs function which always resolves to `undefined` – Bergi Jun 30 '16 at 13:34
  • Can you show an example of your `promiseWhile` in action? Pretty confused over here on how to actually use it. – Jake Wilson Nov 13 '16 at 06:29
  • What is `value` in the first example? – Jake Wilson Nov 14 '16 at 03:33
  • 1
    @JakeWilson It's the initial value for your predicate to check You can omit.it when the test function doesn't take any arguments. – Bergi Nov 14 '16 at 12:19
  • Thank you @Bergi. Was your example comment removed? Could you add the example to the answer maybe? – Jake Wilson Nov 14 '16 at 19:04
  • @JakeWilson The previous comment was wrong so I deleted it. – Bergi Nov 14 '16 at 19:34
  • `var i=0; promiseWhile(()=>i<10, ()=>{i++, return Promise.delay(20)})` (without a `value`) or `var i=0; promiseWhile(x=>x<10, ()=>Promise.delay(20, ++i), i)` – Bergi Nov 14 '16 at 19:36
  • Your code definitely doesn't work. Your example has issues as well as your original code (`condition` vs `predicate`??) If you could post a working example on `codepen` or something that would help. – Jake Wilson Dec 03 '16 at 05:05
  • @JakeWilson `predicate` is the function, `condition` is the boolean it returned – Bergi Dec 03 '16 at 14:26
3

I prefer this implementation as its easier to simulate break and continue with it:

var Continue = {}; // empty object serves as unique value
var again = _ => Continue;

var repeat = fn => Promise.try(fn, again)
  .then(val => val === Continue && repeat(fn) || val);

Example 1: stops when either the source or the destination indicate an error

repeat(again => 
    source.read()
    .then(data => destination.write(data))
    .then(again)

Example 2: stop randomly if the coin flip given 90% probability results with a 0

var blah = repeat(again =>
    Promise.delay(1000)
    .then(_ => console.log("Hello"))
    .then(_ => flipCoin(0.9) && again() || "blah"));

Example 3: Loop with condition that returns the sum:

repeat(again => {
  if (sum < 100) 
    return fetchValue()
      .then(val => sum += val)
      .then(again));
  else return sum;
})
Gjorgi Kjosev
  • 1,559
  • 14
  • 24