-
-
Save victorquinn/8030190 to your computer and use it in GitHub Desktop.
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"); | |
}); |
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
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
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?
looks like bluebird changed API , but they have docs how to get "old" defered()
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);
});
};
Do you know of a way to do this in plain js without using node.js (i.e. without using process.nextTick() )? Thanks.
There are process.nextTick polyfills or examples around, like here: https://gist.github.com/WebReflection/2953527
you could also just call loop() if your action is doing I/O
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);
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));
});
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 }
@mkbitz, won't your solution run out of memory unless the JS engine supports tail call optimization?
@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...
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");
});
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");
});
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");
});
@briancavalier very cool, didn't know about that!
Perhaps this would be good as a pull request for the Bluebird library then...