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