Skip to content

Instantly share code, notes, and snippets.

@victorquinn
Last active March 30, 2023 04:29
Show Gist options
  • Save victorquinn/8030190 to your computer and use it in GitHub Desktop.
Save victorquinn/8030190 to your computer and use it in GitHub Desktop.
Promise "loop" using the Bluebird library
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;
};
// And below is a sample usage of this promiseWhile function
var sum = 0,
stop = 10;
promiseWhile(function() {
// Condition for stopping
return sum < stop;
}, function() {
// The function to run, should return a promise
return new Promise(function(resolve, reject) {
// Arbitrary 250ms async method to simulate async process
setTimeout(function() {
sum++;
// Print out the sum thus far to show progress
console.log(sum);
resolve();
}, 250);
});
}).then(function() {
// Notice we can chain it because it's a Promise, this will run after completion of the promiseWhile Promise!
console.log("Done");
});
@victorquinn
Copy link
Author

@briancavalier very cool, didn't know about that!

Perhaps this would be good as a pull request for the Bluebird library then...

@spion
Copy link

spion commented Dec 19, 2013

Here is what I came up with a couple of days ago, using the awesomeness that are ES6 arrow functions and some of the other neat methods in Bluebird :D

https://gist.github.com/spion/8009559

@petkaantonov
Copy link

Doing it without helpers can be acceptable too

(function loop(sum, stop) {
    if (sum < stop) {
        return Promise.delay(250).then(() => {
            sum++;
            console.log(sum);
            return loop(sum, stop);
        });
    }
})(0, 10).then(() => console.log('Done'))

I mean considering some people are doing the same even with synchronous code just so that their code would be recursive, it's not pretty bad. And unlike in synchronous version the call stack doesn't keep growing here :P

@brettkiefer
Copy link

It looks like maybe (at least with the current Bluebird) you want to bind the resolver.reject catch to the resolver, or on an error you'll get a promise error ("Illegal invocation, resolver resolve/reject must be called within a resolver context.") since you won't have the internal promise state?

@syzer
Copy link

syzer commented Nov 14, 2014

looks like bluebird changed API , but they have docs how to get "old" defered()

@noam3127
Copy link

Here's an updated example using the constructor instead of Promise.defer()

var promiseWhile = function(condition, action) {
  return new Promise(function(resolve, reject) {
    var loop = function() {
      if (!condition()) return resolve();
      return Promise.cast(action())
        .then(loop)
        .catch(function(e) {
          reject(e);
        });
    };
    process.nextTick(loop);
  });
};

@simonlcparry
Copy link

Do you know of a way to do this in plain js without using node.js (i.e. without using process.nextTick() )? Thanks.

@jtwalters
Copy link

There are process.nextTick polyfills or examples around, like here: https://gist.github.com/WebReflection/2953527

@beders
Copy link

beders commented Jun 24, 2015

you could also just call loop() if your action is doing I/O

@rodrigoney
Copy link

Updating @noam3127, if you want to return a promise of an array from your loop :

 var promiseWhile = function(condition, action) {
    return new Promise(function(resolve, reject) {
      var loop = function(result) {
        if (result === null || result === undefined) result = [];
        if (!condition()) return resolve(result);
        return Promise.join(action(), result, function(a,b){
            return a.concat(b);
          })
          .then(loop)
          .catch(function(e) {
            reject(e);
          });
      };
      process.nextTick(loop);
    });
  };
}

and, of course, you have to return it from your action() function :

setTimeout(function() {
     ...
    resolve(*YOUR ARRAY*);
}, 250);

@p1nox
Copy link

p1nox commented Nov 8, 2015

I was digging a bit about this and I found some interesting links:

petkaantonov/bluebird#553 (comment)
http://stackoverflow.com/a/29396005
http://stackoverflow.com/a/24660323

Simplifications:

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

Or

var promiseWhile = Promise.method(function(condition, action) {
    if (!condition()) return;
    return action().then(promiseWhile.bind(null, condition, action));
});

@mklbtz
Copy link

mklbtz commented Jan 27, 2016

For anyone who doesn't want to use process.nextTick(), I've written my own solution here. I'm using the Q framework, but it could be very easily reworked to use Bluebird. It returns a promise which will resolve to the final iteration's return value, so it works a lot like reduce.

function promiseUntil(correct, action, last) {
  if (last === undefined || last.then === undefined) { last = Q(last); }
  return last.then(action).then(function (v) {
    if (correct(v)) {
      return Q(v);
    } else {
      return promiseUntil(correct, action, Q(v));
    }
  });
}

That is the promise-y equivalent of this function:

function loopUntil(correct, action, last) {
  var value = action(last);
  if (correct(value)) {
    return value;
  } else {
    return loopUntil(correct, action, value);
  }
}

Usage is straightforward:

function equalFour(v) {
  return v === 4;
}

function increment(v) {
  console.log(v);
  return (v === undefined) ? 0 : v+1;
}

var p = promiseUntil(equalFour, increment);
// prints:
// undefined
// 0
// 1
// 2
// 3

p = promiseUntil(equalFour, increment, 0);
// prints:
// 0
// 1
// 2
// 3

console.log(p);
// { state: 'fulfilled', value: 4 }

@ejc3
Copy link

ejc3 commented May 5, 2016

@mkbitz, won't your solution run out of memory unless the JS engine supports tail call optimization?

@lktvlm
Copy link

lktvlm commented May 19, 2016

@petkaantonov your version crashes in chrome with RangeError: Maximum call stack size exceeded.

Still looking for and elegant alternative for bluebird that does actually work...

@alexcorvi
Copy link

ES6 native implementation

        var promiseWhile = function(condition, action) {
            var resolver = Promise.defer();
            var loop = function() {
                if (!condition()) return resolver.resolve();
                return new Promise(action)
                    .then(loop)
                    .catch(resolver.reject);
            };
            process.nextTick(loop);
            return resolver.promise;
        };


        var sum = 0, stop = 10;

        promiseWhile(function() {
            return sum < stop;
        },function(resolve,reject){
            setTimeout(function() {
                sum++;
                console.log(sum);
                resolve();
            }, 250);
        }).then(function() {
            console.log("Done");
        });

@alexcorvi
Copy link

alexcorvi commented Sep 4, 2016

For loop in an array

promiseFor = Promise.method(function(arr,action,steps) {
    "use strict";
    if(!steps) steps = 0;
    if(arr.length<=steps) return;
    return new Promise((resolve,reject)=>{
        try {
            action(steps);
            resolve();
         }
         catch(e) {
             reject(e);
         }
    }).then(Promise.for.bind(null,arr,action,steps+1));
});

Usage

asyncFunc()
.then(()=>{
    var arr = [1,2,3]
    return new promiseFor(arr,(i)=>{ console.log(i) });
})
.then(()=>{
    console.log("done iterating");
});

@alexcorvi
Copy link

Doing async function on each members of the array

global.Promise.asyncOnEach = Promise.method(function(arr,action,steps) {
    if(!steps) steps = 0;
    console.log("Currently working on index:",steps,"Value:",arr[steps]);
    if(arr.length<=steps) {
        console.log("Finished working on the array, now will return it");
        console.log(arr);
        return arr;
    }
    return new Promise((resolve,reject)=>{
        action(arr[steps]).then((v)=>{
            arr[steps] = v;
            console.log(v);
            resolve(arr);
        }).catch((e)=>{
            reject(e);
        });
    }).then(Promise.asyncOnEach.bind(null,arr,action,steps+1));
});

Usage:

Promise.asyncOnEach([3,6,9],asyncFunc)
.then((arr)=>{
    console.log("The result is:",arr);
    res.send("OK");
}).catch((e)=>{
    console.log(e);
    res.send("error");
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment