While loop with promises
Here's a reusable function that I think is pretty clear.
var Q = require("q");// `condition` is a function that returns a boolean// `body` is a function that returns a promise// returns a promise for the completion of the loopfunction promiseWhile(condition, body) { var done = Q.defer(); function loop() { // When the result of calling `condition` is no longer true, we are // done. if (!condition()) return done.resolve(); // Use `when`, in case `body` does not return a promise. // When it completes loop again otherwise, if it fails, reject the // done promise Q.when(body(), loop, done.reject); } // Start running the loop in the next tick so that this function is // completely async. It would be unexpected if `body` was called // synchronously the first time. Q.nextTick(loop); // The promise return done.promise;}// Usagevar index = 1;promiseWhile(function () { return index <= 11; }, function () { console.log(index); index++; return Q.delay(500); // arbitrary async}).then(function () { console.log("done");}).done();
This is the simplest way I've found to express the basic pattern: you define a function that calls the promise, checks its result, and then either calls itself again or terminates.
const doSomething = value => new Promise(resolve => setTimeout(() => resolve(value >= 5 ? 'ok': 'no'), 1000))const loop = value => doSomething(value).then(result => { console.log(value) if (result === 'ok') { console.log('yay') } else { return loop(value + 1) } })loop(1).then(() => console.log('all done!'))
If you were using a promise that resolves or rejects, you would define then
and catch
instead of using an if-clause.
If you had an array of promises, you would just change loop
to shift or pop the next one each time.
EDIT: Here's a version that uses async/await
, because it's 2018:
const loop = async value => { let result = null while (result != 'ok') { console.log(value) result = await doSomething(value) value = value + 1 } console.log('yay')}
As you can see, it uses a normal while loop and no recursion.
I'd use an object to wrap the value. That way you can have a done
property to let the loop know you're done.
// fn should return an object like// {// done: false,// value: foo// }function loop(promise, fn) { return promise.then(fn).then(function (wrapper) { return !wrapper.done ? loop(Q(wrapper.value), fn) : wrapper.value; });}loop(Q.resolve(1), function (i) { console.log(i); return { done: i > 10, value: i++ };}).done(function () { console.log('done');});