Instantly share code, notes, and snippets.

Embed
What would you like to do?
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

This comment has been minimized.

Show comment
Hide comment
@victorquinn

victorquinn Dec 19, 2013

Note, if you want to clone this and run this gist, you will want to run npm install bluebird in its directory after cloning it to ensure its only dependency is installed.

Then you can run node promise_while_loop.js to see it do its rather boring task of printing the numbers 1 through 10 with a delay between each number.

Owner

victorquinn commented Dec 19, 2013

Note, if you want to clone this and run this gist, you will want to run npm install bluebird in its directory after cloning it to ensure its only dependency is installed.

Then you can run node promise_while_loop.js to see it do its rather boring task of printing the numbers 1 through 10 with a delay between each number.

@briancavalier

This comment has been minimized.

Show comment
Hide comment
@briancavalier

briancavalier Dec 19, 2013

Cool. There is an operation in the functional programming lexicon known as "unfold", which is very similar. when.js provides an unfold module that implements it--thought you might be interested to compare and contrast.

briancavalier commented Dec 19, 2013

Cool. There is an operation in the functional programming lexicon known as "unfold", which is very similar. when.js provides an unfold module that implements it--thought you might be interested to compare and contrast.

@victorquinn

This comment has been minimized.

Show comment
Hide comment
@victorquinn

victorquinn Dec 19, 2013

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

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

Owner

victorquinn commented Dec 19, 2013

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

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

@spion

This comment has been minimized.

Show comment
Hide comment
@spion

spion 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

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

This comment has been minimized.

Show comment
Hide comment
@petkaantonov

petkaantonov Dec 21, 2013

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

petkaantonov commented Dec 21, 2013

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

This comment has been minimized.

Show comment
Hide comment
@brettkiefer

brettkiefer Nov 4, 2014

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?

brettkiefer commented Nov 4, 2014

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

This comment has been minimized.

Show comment
Hide comment
@syzer

syzer Nov 14, 2014

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

syzer commented Nov 14, 2014

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

@noam3127

This comment has been minimized.

Show comment
Hide comment
@noam3127

noam3127 May 12, 2015

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

noam3127 commented May 12, 2015

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

This comment has been minimized.

Show comment
Hide comment
@simonlcparry

simonlcparry May 29, 2015

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

simonlcparry commented May 29, 2015

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

@jtwalters

This comment has been minimized.

Show comment
Hide comment
@jtwalters

jtwalters Jun 3, 2015

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

jtwalters commented Jun 3, 2015

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

@beders

This comment has been minimized.

Show comment
Hide comment
@beders

beders Jun 24, 2015

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

beders commented Jun 24, 2015

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

@rodrigoney

This comment has been minimized.

Show comment
Hide comment
@rodrigoney

rodrigoney Sep 17, 2015

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);

rodrigoney commented Sep 17, 2015

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

This comment has been minimized.

Show comment
Hide comment
@p1nox

p1nox 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));
});

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

This comment has been minimized.

Show comment
Hide comment
@mklbtz

mklbtz 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 }

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

This comment has been minimized.

Show comment
Hide comment
@ejc3

ejc3 May 5, 2016

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

ejc3 commented May 5, 2016

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

@lktvlm

This comment has been minimized.

Show comment
Hide comment
@lktvlm

lktvlm 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...

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

This comment has been minimized.

Show comment
Hide comment
@alexcorvi

alexcorvi Sep 4, 2016

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 commented Sep 4, 2016

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

This comment has been minimized.

Show comment
Hide comment
@alexcorvi

alexcorvi 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 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

This comment has been minimized.

Show comment
Hide comment
@alexcorvi

alexcorvi Sep 4, 2016

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

alexcorvi commented Sep 4, 2016

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