Skip to content

Instantly share code, notes, and snippets.

@victorquinn
Last active March 30, 2023 04:29
Show Gist options
  • Select an option

  • Save victorquinn/8030190 to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown
Author

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

Copy link
Copy Markdown

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

Copy link
Copy Markdown
Author

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

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

@spion

spion commented Dec 19, 2013

Copy link
Copy Markdown

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
Copy Markdown

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
Copy Markdown

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

syzer commented Nov 14, 2014

Copy link
Copy Markdown

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

@noam3127

Copy link
Copy Markdown

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
Copy Markdown

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
Copy Markdown

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

@beders

beders commented Jun 24, 2015

Copy link
Copy Markdown

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

@rodrigoney

Copy link
Copy Markdown

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

p1nox commented Nov 8, 2015

Copy link
Copy Markdown

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

mklbtz commented Jan 27, 2016

Copy link
Copy Markdown

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

ejc3 commented May 5, 2016

Copy link
Copy Markdown

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

@lktvlm

lktvlm commented May 19, 2016

Copy link
Copy Markdown

@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
Copy Markdown

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

alexcorvi commented Sep 4, 2016

Copy link
Copy Markdown

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
Copy Markdown

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