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
To use Javascript promises in a for loop, use async / await . This waits for each promiseAction to complete before continuing to the next iteration in the loop. In this guide, you learn how async/await works and how it solves the problem of using promises in for loops.
In single-core CPU the promises would run concurrently and in multi-core CPU they can be executed (!) in parallel for CPU intensive tasks. As explained earlier that most of the modern computers can do I/O in parallel.
Nested Promise: In a promise nesting when you return a promise inside a then method, and if the returned promise is already resolved/rejected, it will immediately call the subsequent then/catch method, if not it will wait. If promised is not return, it will execute parallelly.
all executes them in parallel.
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();
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.
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');
});
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);
})
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:
Stuart's answer is for a more generic solution, but the basics are awesome (once you realize how it works).
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');
});
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);
});
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}`));
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())
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);
})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With