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");
});
@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