While a cycle with promises

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

do something if the condition is still there, do it again, then do something else.

dosomething.then(possilblydomoresomethings).then(finish) 

I did it this way, I was wondering if there are 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')}); 

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

+66
promise loops q
Jun 20 '13 at 15:28
source share
12 answers

I would use an object to transfer the value. That way, you can have the done property so that the loop knows that you are 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'); }); 
+16
Jun 21 '13 at 15:31
source share

Here's a reusable feature that I think is pretty straightforward.

 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(); 
+57
Jun 21 '13 at 15:18
source share

This is the easiest way to express a basic pattern: you define a function that calls a 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!')) 

Watch it in action on JSBin

If you used a promise that allows or rejects, you should define then and catch instead of using if-clause.

If you had an array of promises, you would just change the loop to a shift or add the next one each time.




EDIT: Here is the version using async/await because it is 2018:

 const loop = async value => { let result = null while (result != 'ok') { console.log(value) result = await doSomething(value) value = value + 1 } console.log('yay') } 

Watch it in action on CodePen

As you can see, it uses the usual while loop and not recursion.

+17
Jul 15 '16 at 1:44
source share

This is for bluebird, not q, but since you did not specify q specifically .. in the bluebird poster, the author mentions that returning the function to generate promises would be more idiomatic than using deferred ones.

 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); }) 
+12
Jun 17 '14 at 17:55
source share

Since I cannot comment on Stuart K's answer, I will add a little here. Based on Stuart K's answer, you can wrap it up to a surprisingly simple concept: repeat an unfulfilled promise. What he has is essentially:

  • Create a new pending promise instance.
  • Define your function that you want to call in a loop
  • Inside this function:
    • Check if everything is done; and when you resolve the promise made at # 1 and return it.
    • If you're not done, tell Q to use the existing promise and run an invalid function, which is a "recursive" function, or crash if it died. Q.when (promise, yourFunction, failFunction)
  • After defining your function, use Q to run the function for the first time using Q.nextTick (yourFunction)
  • Finally return your new promise to the caller (which will lead to the launch of all this).

Stuart's answer is a more general solution, but the basics are awesome (as soon as you understand how this works).

+5
May 7 '15 at 21:52
source share

Now this pattern is easier to call with q-flow . For 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'); }); 
+4
May 14 '14 at 9:58
source share

Here are the extensions to the Promise prototype to simulate the behavior of a for loop. It supports promises or immediate values ​​for initialization, state, loop, and increment sections. It also fully supports exceptions, and it has no memory leaks. The following is an example of 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); }); 
+3
Dec 25 '14 at 20:44
source share
 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()) 
+1
Feb 18 '16 at 18:16
source share

Now I use 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); } 

It takes an arr array and a work function and returns Promise . The above function is called once for each element of the array and receives the passed current element and index in the array. It can be synchronization or asynchrony, in which case it should return Promise.

You can use it as follows:

 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 element of the array will be processed in turn. After everything has been processed, the code specified in .then() will be launched, or, if some error occurs, the code specified for .catch() . Inside the work function, you can throw a Error (in the case of synchronous functions) or reject Promise (in the case of asynchronous functions) to break 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); }) 
+1
Jun 18 '16 at 18:53
source share

Using the promise of ES6, I came up with this. It combines promises and returns a promise. This is not technically a while loop, but shows 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 is my violin.

0
Nov 11 '15 at 19:08
source share

I thought I could also throw a 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 the one passed to the Promise constructor, but will be called again until it activates the Call Back success. The before_retry function allows you to configure errors for failed attempts. If it returns true value, it will be considered a form of success, and the "cycle" will end, and this will result in truthfulness. If the before_retry function before_retry not registered or does not return false, then the loop will execute for another iteration. The third option is that the before_retry function throws an error. If this happens, the "cycle" will end, passing this 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 
0
Mar 03 '16 at 20:32
source share

I wrote a module that will help you do chain cycles of asynchronous tasks using 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

-one
Oct 21 '15 at 19:42
source share



All Articles