While loop with promises

node.jsLoopsPromiseQ

node.js Problem Overview


What would be the idiomatic way to do something like a while loop with promises. So:

do something if the condition still stands do it again repeat then do something else.

dosomething.then(possilblydomoresomethings).then(finish)

I've done it this way I was wondering if there were any better/more idomatic ways?

var q = require('q');

var index = 1;

var useless =  function(){
        var currentIndex = index;
        console.log(currentIndex)
        var deferred = q.defer();
        setTimeout(function(){
            if(currentIndex > 10)
                deferred.resolve(false);
            else deferred.resolve(true);
            },500);
        return deferred.promise;
    }

var control = function(cont){
        var deferred = q.defer();
        if(cont){
                index = index + 1;
                useless().then(control).then(function(){
                        deferred.resolve();
                    });
            }
         else deferred.resolve();
        return deferred.promise;
    }

var chain = useless().then(control).then(function(){console.log('done')});

Output: 1 2 3 4 5 6 7 8 9 10 11 done

node.js Solutions


Solution 1 - node.js

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 loop
function 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;
}


// Usage
var index = 1;
promiseWhile(function () { return index <= 11; }, function () {
    console.log(index);
    index++;
    return Q.delay(500); // arbitrary async
}).then(function () {
    console.log("done");
}).done();

Solution 2 - node.js

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!'))

See it in action on JSBin

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

See it in action on CodePen

As you can see, it uses a normal while loop and no recursion.

Solution 3 - node.js

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

Solution 4 - node.js

This is for bluebird not q but since you didn't mention q specifically.. in the bluebird api doc the author mentions returning a promise-generating function would be more idiomatic than using deferreds.

var Promise = require('bluebird');
var i = 0;

var counter = Promise.method(function(){
	return i++;
})

function getAll(max, results){
	var results = results || [];
	return counter().then(function(result){
		results.push(result);
		return (result < max) ? getAll(max, results) : results
	})
}

getAll(10).then(function(data){
	console.log(data);
})

Solution 5 - node.js

Since I can't comment on Stuart K's answer I'll add a little bit here. Based on Stuart K's answer you can boil it down to a surprisingly simple concept: Reuse an unfulfilled promise. What he has is essentially:

  1. Create a new instance of a deferred promise
  2. Define your function that you want to call in a loop
  3. Inside that function:
    1. Check to see if you're done; and when you are resolve the promise created in #1 and return it.
    2. If you are not done then tell Q to use the existing promise and run the unfullfilled function that is the "recursive" function, or fail if it died. Q.when(promise, yourFunction, failFunction)
  4. After defining your function use Q to trigger the function for the first time using Q.nextTick(yourFunction)
  5. Finally return your new promise to the caller (which will trigger the whole thing to start).

Stuart's answer is for a more generic solution, but the basics are awesome (once you realize how it works).

Solution 6 - node.js

This pattern is now more easily called by using q-flow. An example, for the above problem:

var q = require('q');
require('q-flow');
var index = 1;
q.until(function() {
  return q.delay(500).then(function() {
    console.log(index++);
    return index > 10;
  });
}).done(function() {
  return console.log('done');
});

Solution 7 - node.js

Here is an extensions to the Promise prototype to mimic the behavior of a for loop. It supports promises or immediate values for the initialization, condition, loop body, and increment sections. It also has full support for exceptions, and it does not have memory leaks. An example is given below on how to use it.

var Promise = require('promise');


// Promise.loop([properties: object]): Promise()
//
//	Execute a loop based on promises. Object 'properties' is an optional
//	argument with the following fields:
//
//	initialization: function(): Promise() | any, optional
//
//		Function executed as part of the initialization of the loop. If
//		it returns a promise, the loop will not begin to execute until
//		it is resolved.
//
//		Any exception occurring in this function will finish the loop
//		with a rejected promise. Similarly, if this function returns a
//		promise, and this promise is reject, the loop finishes right
//		away with a rejected promise.
//
//	condition: function(): Promise(result: bool) | bool, optional
//
//		Condition evaluated in the beginning of each iteration of the
//		loop. The function should return a boolean value, or a promise
//		object that resolves with a boolean data value.
//
//		Any exception occurring during the evaluation of the condition
//		will finish the loop with a rejected promise. Similarly, it this
//		function returns a promise, and this promise is rejected, the
//		loop finishes right away with a rejected promise.
//
//		If no condition function is provided, an infinite loop is
//		executed.
//
//	body: function(): Promise() | any, optional
//
//		Function acting as the body of the loop. If it returns a
//		promise, the loop will not proceed until this promise is
//		resolved.
//
//		Any exception occurring in this function will finish the loop
//		with a rejected promise. Similarly, if this function returns a
//		promise, and this promise is reject, the loop finishes right
//		away with a rejected promise.
//
//	increment: function(): Promise() | any, optional
//
//		Function executed at the end of each iteration of the loop. If
//		it returns a promise, the condition of the loop will not be
//		evaluated again until this promise is resolved.
//
//		Any exception occurring in this function will finish the loop
//		with a rejected promise. Similarly, if this function returns a
//		promise, and this promise is reject, the loop finishes right
//		away with a rejected promise.
//
Promise.loop = function(properties)
{
	// Default values
	properties = properties || {};
	properties.initialization = properties.initialization || function() { };
	properties.condition = properties.condition || function() { return true; };
	properties.body = properties.body || function() { };
	properties.increment = properties.increment || function() { };

	// Start
	return new Promise(function(resolve, reject)
	{
		var runInitialization = function()
		{
			Promise.resolve().then(function()
			{
				return properties.initialization();
			})
			.then(function()
			{
				process.nextTick(runCondition);
			})
			.catch(function(error)
			{
				reject(error);
			});
		}

		var runCondition = function()
		{
			Promise.resolve().then(function()
			{
				return properties.condition();
			})
			.then(function(result)
			{
				if (result)
					process.nextTick(runBody);
				else
					resolve();
			})
			.catch(function(error)
			{
				reject(error);
			});
		}

		var runBody = function()
		{
			Promise.resolve().then(function()
			{
				return properties.body();
			})
			.then(function()
			{
				process.nextTick(runIncrement);
			})
			.catch(function(error)
			{
				reject(error);
			});
		}

		var runIncrement = function()
		{
			Promise.resolve().then(function()
			{
				return properties.increment();
			})
			.then(function()
			{
				process.nextTick(runCondition);
			})
			.catch(function(error)
			{
				reject(error);
			});
		}

		// Start running initialization
		process.nextTick(runInitialization);
	});
}


// Promise.delay(time: double): Promise()
//
//	Returns a promise that resolves after the given delay in seconds.
//
Promise.delay = function(time)
{
	return new Promise(function(resolve)
	{
		setTimeout(resolve, time * 1000);
	});
}


// Example
var i;
Promise.loop({
	initialization: function()
	{
		i = 2;
	},
	condition: function()
	{
		return i < 6;
	},
	body: function()
	{
		// Print "i"
		console.log(i);

		// Exception when 5 is reached
		if (i == 5)
			throw Error('Value of "i" reached 5');

		// Wait 1 second
		return Promise.delay(1);
	},
	increment: function()
	{
		i++;
	}
})
.then(function()
{
	console.log('LOOP FINISHED');
})
.catch(function(error)
{
	console.log('EXPECTED ERROR:', error.message);
});

Solution 8 - node.js

Here is a generic solution that uses ES6 promises:

/**
 * Simulates a while loop where the condition is determined by the result of a Promise.
 *
 * @param {Function} condition
 * @param {Function} action
 * @returns {Promise}
 */
function promiseWhile (condition, action) {
    return new Promise((resolve, reject) => {
        const loop = function () {
            if (!condition()) {
                resolve();
            } else {
                Promise.resolve(action())
                    .then(loop)
                    .catch(reject);
            }
        }
        loop();
    })
}

/**
 * Simulates a do-while loop where the condition is determined by the result of a Promise.
 *
 * @param {Function} condition
 * @param {Function} action
 * @returns {Promise}
 */
function promiseDoWhile (condition, action) {
    return Promise.resolve(action())
        .then(() => promiseWhile(condition, action));
}

export default promiseWhile;
export {promiseWhile, promiseDoWhile};

And you can use it like this:

let myCounter = 0;

function myAsyncFunction () {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log(++myCounter);
            resolve()
        }, 1000)
    });
}


promiseWhile(() => myCounter < 5, myAsyncFunction).then(() => console.log(`Timer completed: ${myCounter}`));

Solution 9 - node.js

var Q = require('q')

var vetor  = ['a','b','c']

function imprimeValor(elements,initValue,defer){
   
    console.log( elements[initValue++] )
    defer.resolve(initValue)
    return defer.promise
}

function Qloop(initValue, elements,defer){

    Q.when( imprimeValor(elements, initValue, Q.defer()), function(initValue){

        if(initValue===elements.length){
            defer.resolve()
        }else{
            defer.resolve( Qloop(initValue,elements, Q.defer()) )
        }
    }, function(err){

        defer.reject(err)
    })

    return defer.promise
}

Qloop(0, vetor,Q.defer())

Solution 10 - node.js

I am now using this:

function each(arr, work) {
  function loop(arr, i) {
    return new Promise(function(resolve, reject) {
      if (i >= arr.length) {resolve();}
      else try {
        Promise.resolve(work(arr[i], i)).then(function() { 
          resolve(loop(arr, i+1))
        }).catch(reject);
      } catch(e) {reject(e);}
    });
  }
  return loop(arr, 0);
}

This accepts an array arr and a function work and returns a Promise. The supplied function gets called once for each element in the array and gets passed the current element and it's index in the array. It may be sync or async, in which case it must return a Promise.

You can use it like this:

var items = ['Hello', 'cool', 'world'];
each(items, function(item, idx) {
    // this could simply be sync, but can also be async
    // in which case it must return a Promise
    return new Promise(function(resolve){
        // use setTimeout to make this async
        setTimeout(function(){
            console.info(item, idx);
            resolve();
        }, 1000);
    });
})
.then(function(){
    console.info('DONE');
})
.catch(function(error){
    console.error('Failed', error);
})

Each item in the array will be handled in turn. Once all are handled, the code given to .then() will run, or, if some error occurred, the code given to .catch(). Inside the work function, you can throw an Error (in case of synchronous functions) or reject the Promise (in case of async functions) to abort the loop.

function each(arr, work) {
  function loop(arr, i) {
    return new Promise(function(resolve, reject) {
      if (i >= arr.length) {resolve();}
      else try {
        Promise.resolve(work(arr[i], i)).then(function() { 
          resolve(loop(arr, i+1))
        }).catch(reject);
      } catch(e) {reject(e);}
    });
  }
  return loop(arr, 0);
}

var items = ['Hello', 'cool', 'world'];
each(items, function(item, idx) {
  // this could simply be sync, but can also be async
  // in which case it must return a Promise
  return new Promise(function(resolve){
    // use setTimeout to make this async
    setTimeout(function(){
      console.info(item, idx);
      resolve();
    }, 1000);
  });
})
.then(function(){
  console.info('DONE');
})
.catch(function(error){
  console.error('Failed', error);
})

Solution 11 - node.js

Using the ES6 Promise, I came up with this. It chains the promises and returns a promise. It's not technically a while loop, but does show how to iterate over promises synchronously.

function chain_promises(list, fun) {
    return list.reduce(
        function (promise, element) {
            return promise.then(function () {
                // I only needed to kick off some side-effects. If you need to get
                // a list back, you would append to it here. Or maybe use
                // Array.map instead of Array.reduce.
                fun(element);
            });
    	},
        // An initial promise just starts things off.
        Promise.resolve(true)
    );
}

// To test it...

function test_function (element) {
    return new Promise(function (pass, _fail) {
        console.log('Processing ' + element);
        pass(true);
    });
}

chain_promises([1, 2, 3, 4, 5], test_function).then(function () {
    console.log('Done.');
});

Here's my fiddle.

Solution 12 - node.js

I thought I might as well throw my hat in the ring, using ES6 Promises...

function until_success(executor){
    var before_retry = undefined;
    var outer_executor = function(succeed, reject){
        var rejection_handler = function(err){
            if(before_retry){
                try {
                    var pre_retry_result = before_retry(err);
                    if(pre_retry_result)
                        return succeed(pre_retry_result);
                } catch (pre_retry_error){
                    return reject(pre_retry_error);
                }
            }
            return new Promise(executor).then(succeed, rejection_handler);                
        }
        return new Promise(executor).then(succeed, rejection_handler);
    }

    var outer_promise = new Promise(outer_executor);
    outer_promise.before_retry = function(func){
        before_retry = func;
        return outer_promise;
    }
    return outer_promise;
}

The executor argument is the same as that passed to a Promise constructor, but will be called repeatedly until it triggers the success callback. The before_retry function allows for custom error handling on the failed attempts. If it returns a truthy value it will be considered a form of success and the "loop" will end, with that truthy as the result. If no before_retry function is registered, or it returns a falsey value, then the loop will run for another iteration. The third option is that the before_retry function throws an error itself. If this happens, then the "loop" will end, passing that error as an error.


Here is an example:

var counter = 0;
function task(succ, reject){
    setTimeout(function(){
        if(++counter < 5)
            reject(counter + " is too small!!");
        else
            succ(counter + " is just right");
    }, 500); // simulated async task
}

until_success(task)
        .before_retry(function(err){
            console.log("failed attempt: " + err);
            // Option 0: return falsey value and move on to next attempt
            // return

            // Option 1: uncomment to get early success..
            //if(err === "3 is too small!!") 
            //    return "3 is sort of ok"; 

            // Option 2: uncomment to get complete failure..
            //if(err === "3 is too small!!") 
            //    throw "3rd time, very unlucky"; 
  }).then(function(val){
       console.log("finally, success: " + val);
  }).catch(function(err){
       console.log("it didn't end well: " + err);
  })

Output for option 0:

failed attempt: 1 is too small!!
failed attempt: 2 is too small!!
failed attempt: 3 is too small!!
failed attempt: 4 is too small!!
finally, success: 5 is just right

Output for option 1:

failed attempt: 1 is too small!!
failed attempt: 2 is too small!!
failed attempt: 3 is too small!!
finally, success: 3 is sort of ok

Output for option 2:

failed attempt: 1 is too small!!
failed attempt: 2 is too small!!
failed attempt: 3 is too small!!
it didn't end well: 3rd time, very unlucky

Solution 13 - node.js

Lots of answers here and what you are trying to achieve is not very practical. but this should work. This was implemented in an aws lambda function, with Node.js 10 it will go until function timeout. It may also consume a decent amount of memory.

exports.handler = async (event) => {
  let res = null;
  while (true) {
    try{
     res = await dopromise();
    }catch(err){
     res = err;
    }
    console.log(res);
   }//infinite will time out
  };

  function dopromise(){
   return new Promise((resolve, reject) => {
    //do some logic
    //if error reject
        //reject('failed');
    resolve('success');
  });
}

Tested on lambda and running fine for over 5 min. But as stated by others this is not a good thing to do.

Solution 14 - node.js

I wrote a module which helps you do chained loops of asynchronous tasks with promises, it is based on the answer above provided by juandopazo

/**
 * Should loop over a task function which returns a "wrapper" object
 * until wrapper.done is true. A seed value wrapper.seed is propagated to the
 * next run of the loop.
 *
 * todo/maybe? Reject if wrapper is not an object with done and seed keys.
 *
 * @param {Promise|*} seed
 * @param {Function} taskFn
 *
 * @returns {Promise.<*>}
 */
function seedLoop(seed, taskFn) {
  const seedPromise = Promise.resolve(seed);

  return seedPromise
    .then(taskFn)
    .then((wrapper) => {
      if (wrapper.done) {
        return wrapper.seed;
      }

      return seedLoop(wrapper.seed, taskFn);
    });
}

// A super simple example of counting to ten, which doesn't even
// do anything asynchronous, but if it did, it should resolve to 
// a promise that returns the { done, seed } wrapper object for the
// next call of the countToTen task function.
function countToTen(count) {
  const done = count > 10;
  const seed = done ? count : count + 1;

  return {done, seed};
}

seedLoop(1, countToTen).then((result) => {
  console.log(result); // 11, the first value which was over 10.
});

https://github.com/CascadeEnergy/promise-seedloop

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionGrummleView Question on Stackoverflow
Solution 1 - node.jsStuart KView Answer on Stackoverflow
Solution 2 - node.jslawrenceView Answer on Stackoverflow
Solution 3 - node.jsjuandopazoView Answer on Stackoverflow
Solution 4 - node.jsaarosilView Answer on Stackoverflow
Solution 5 - node.jsmillebiView Answer on Stackoverflow
Solution 6 - node.jsJoe HildebrandView Answer on Stackoverflow
Solution 7 - node.jsuser3707531View Answer on Stackoverflow
Solution 8 - node.jsRory AbrahamView Answer on Stackoverflow
Solution 9 - node.jsLucas RochaView Answer on Stackoverflow
Solution 10 - node.jsStijn de WittView Answer on Stackoverflow
Solution 11 - node.jsmqsohView Answer on Stackoverflow
Solution 12 - node.jsdan-manView Answer on Stackoverflow
Solution 13 - node.jsHans-Eric LippkeView Answer on Stackoverflow
Solution 14 - node.jsnackjicholsonView Answer on Stackoverflow