I am trying to implement a while loop using promises.
The method outlined here seems to work.
http://blog.victorquinn.com/javascript-promise-while-loop
it uses a function like this
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;
};
This seems to use anti-patterns and deprecated methods like cast and defer.
Does anyone know a better or more modern way to accomplish this?
Thanks
cast can be translated to resolve. defer should indeed not be used.
You'd create your loop only by chaining and nesting then invocations onto an initial Promise.resolve(undefined).
function promiseWhile(predicate, action, value) {
return Promise.resolve(value).then(predicate).then(function(condition) {
if (condition)
return promiseWhile(predicate, action, action());
});
}
Here, both predicate and action may return promises. For similar implementations also have a look at Correct way to write loops for promise. Closer to your original function would be
function promiseWhile(predicate, action) {
function loop() {
if (!predicate()) return;
return Promise.resolve(action()).then(loop);
}
return Promise.resolve().then(loop);
}
I prefer this implementation as its easier to simulate break and continue with it:
var Continue = {}; // empty object serves as unique value
var again = _ => Continue;
var repeat = fn => Promise.try(fn, again)
.then(val => val === Continue && repeat(fn) || val);
Example 1: stops when either the source or the destination indicate an error
repeat(again =>
source.read()
.then(data => destination.write(data))
.then(again)
Example 2: stop randomly if the coin flip given 90% probability results with a 0
var blah = repeat(again =>
Promise.delay(1000)
.then(_ => console.log("Hello"))
.then(_ => flipCoin(0.9) && again() || "blah"));
Example 3: Loop with condition that returns the sum:
repeat(again => {
if (sum < 100)
return fetchValue()
.then(val => sum += val)
.then(again));
else return sum;
})
Related
I am stuck trying to build a recursive function that is already defined as a promise.
I have not been able to apply the recursive pattern on the code below which is looping only once even though loopFor is initialised at 20 what I am missing?
Requirement: receivingMessages must be a promise.
let globalMessageArray = [];
let count = 0;
let loopFor = 20;
function receivingMessages(params, loopFor, globalMessageArray) {
return new Promise((resolve, reject) => {
const command = new ReceiveMessageCommand(params);
client.send(command).then(
(data) => {
if (data && data.Messages && data.Messages.length) {
data.Messages.forEach(msg => {
globalMessageArray.push(msg);
});
};
return resolve(globalMessageArray);
},
(error) => {
return reject(error);
}).then(
(globalMessageArray) => {
count = count + 1;
console.log("Loop Count: " + count); // always returns 1
if (loopFor === 1) {
return resolve(globalMessageArray);
} else {
return resolve(receivingMessages(params, loopFor - 1, globalMessageArray));
};
});
});
};
In the first then callback client.send(cmd).then(data => … you return resolve(globalMessageArray). This effectively short-circuit your loop, because a promise can only resolve once. Later call of resolve has no effect.
client.send(cmd).then((data) => {
…
return globalMessageArray;
}, …
Remove first call to resolve should solve your problem.
You said in comment:
Using async/await would imply to rewrite the whole program
No, your understanding of async/await is wrong. Any async function is automatically a promise returning function, which meets your requirement. Async/await is just syntax sugar on top of promise.
This means you can safely rewrite ONLY receivingMessages function without needing to modify other places that call it.
Although there is nothing wrong with vanilla promise, rewriting to async/await will make your code so much cleaner.
async function receivingMessages(params, loopFor, globalMessageArray) {
const command = new ReceiveMessageCommand(params);
const data = await client.send(command);
if (data && data.Messages && data.Messages.length) {
data.Messages.forEach(msg => {
globalMessageArray.push(msg);
});
}
if (loopFor === 1) {
return globalMessageArray;
} else {
return receivingMessages(params, loopFor - 1, globalMessageArray)
};
};
The issue with your code is that the resolve call in the then callback after the client.send promise resolves is returning the result of calling receivingMessages instead of the receivingMessages promise itself. This causes the recursive loop to only execute once.
To fix this, you can change the resolve call to return the result of calling receivingMessages directly:
return receivingMessages(params, loopFor - 1, globalMessageArray);
This will cause the receivingMessages function to be called in a recursive manner until loopFor reaches 1.
You may also want to consider adding a base case to the function to ensure that it terminates, such as adding a check for loopFor being less than or equal to 0 and returning the globalMessageArray in that case.
I have a function which has nested Promises within it. I want to re-execute this function when there is a length value of streamRes.length. Currently, it only executes the code and prints out the length on the console but doesn't re-execute.
let fetchEnrolleesData = () => {
getLastUpdatedEnrollee().then(res => {
let params = path+"enrollees?limit=100";
params += res.last_date ? "&last_updated_at=" + res.last_date.value : '';
fetchEnrolleesDataInStream(res, params).then(streamRes => {
if(streamRes.length) {
console.log(streamRes.length);
fetchEnrolleesData();
}
});
});
}
As evolutionxbox wrote, you need to return the promise returned by getLastUpdatedEnrollee as well as the promise getLastUpdatedEnrollee itself. In general, when calling a promise within a function, you need to return those promises in order to use those values outside of that function.
Here is your code with return added as needed:
let fetchEnrolleesData = () => {
return getLastUpdatedEnrollee().then(res => {
let params = path+"enrollees?limit=100";
params += res.last_date ? "&last_updated_at=" + res.last_date.value : '';
return fetchEnrolleesDataInStream(res, params).then(streamRes => {
if(streamRes.length) {
console.log(streamRes.length);
return fetchEnrolleesData();
}
});
});
}
As an optional side note, you may or may not prefer to use () => … syntax instead of () => { … } to remove the need to write return in the top-level function. You can do that in the top-level function because it has no other statements; this wouldn’t work in the inner function, which has multiple statements. See arrow function expressions for more detail.
let fetchEnrolleesData = () =>
getLastUpdatedEnrollee().then(res => {
// …
});
Note: I cannot use async.
I like to use the reduce pattern in cases where I need to run over an array and execute the same function on its members and return a promise, like so:
function get_count() {
return new Promise(function(resolve, reject) {
resolve(3);
});
}
function recursively_execute(data) {
return new Promise(function(resolve, reject) {
resolve(data);
});
}
function reduce_promise_pattern() {
const get_batch_run_count = get_count();
const batch_process = get_batch_run_count.then((count_value) => {
const run_count = new Array(count_value).fill('batch');
function recursive_function(data) {
console.log('Running batch!');
return recursively_execute(data).then(() => {
return data;
});
}
return run_count.reduce((previous_promise) => {
return previous_promise.then((previous_response) => {
test_data = {
'test': 1
};
return recursive_function(test_data);
})
}, Promise.resolve())
});
return batch_process;
}
This will run 3 times because of the run_count which basically builds an array of 3 items. Although it works, this feels like a hack to me.
This approach works when my list is already pre-defined with unique items and these items, well, individually are used inside that reduce as data that is built upon for example, if I have 3 steps to go through, these 3 steps are all unique and each step's data will be used within that one run...but in my case? I'm just tricking the system to think these are different items.
What is the alternative to this?
You reached the limits of Promise chains, although they work they ain't readable. That's why async / await was introduced to handle exactly these usecases, with them you can just halt all kinds of (nested) loops without having to maintain promises for each:
async function reducePromisePattern() {
for(let i = await getCount(); i >= 0; i--) {
await recursiveFunction({'test': 1 });
}
}
If you can't use / transpile async, you could still write some small helpers to do the looping for you, e.g.:
function loopAsync(times, fn) {
function task() {
times--;
if(times <= 0) return;
return fn().then(task);
}
return Promise.resolve().then(task);
}
function reducePromisePattern() {
return getCount().then(function(count) {
return asyncLoop(count, function() {
return recursiveFunction({ test: 1 });
});
});
}
Here are two options without nesting functions in one another. The first one simply uses a for-loop while the second function uses a recursive solution. The last argument of both solutions is optional and should only be used if you want to pass the return data forward from one run to the next (similar to reduce).
const sleep = () => new Promise(resolve => setTimeout(resolve, Math.random() * 1500 + 500));
// solution #1 - for-loop
function times1(n, callback, init) {
var promise = Promise.resolve(init);
for (; n > 0; --n) {
promise = promise.then(val => callback(val));
}
return promise;
}
// example usage
times1(3, n => {
console.log("solution #1 -", n);
return sleep().then(() => n + 1);
}, 0);
// solution #2 - recursive
function times2(n, callback, init) {
var promise = Promise.resolve(init);
if (n <= 0) return promise;
return promise.then(val => times2(n - 1, callback, callback(val)));
}
// example usage
times2(3, n => {
console.log("solution #2 -", n);
return sleep().then(() => n + 1);
}, 0);
How can i chain a list of promises in RXJS? Every promise needs to be executed when the previous is resolved (work todo is stateful).
The way i'm doing it now feels primitive:
const workTodo = []; // an array of work
const allWork = Observable.create(observer => {
const next= () => {
const currentTodo = workTodo.shift();
if (currentTodo ) {
doTodoAsync(currentTodo)
.then(result => observer.onNext(result))
.then(next);
} else {
observer.onCompleted();
}
};
next();
});
I was thinking something like this:
const workTodo = []; // an array of work
const allWork = Observable
.fromArray(workTodo)
.flatMap(doTodoAsync);
But that basically executes all promises at once.
It seems you were pretty close with your attempt.
You may either specify maximum concurrency of 1 for .flatMap like:
Observable.fromArray(workTodo)
.flatMap(doTodoAsync, 1)
or equivalently use .concatMap instead of .flatMap:
Observable.fromArray(workTodo)
.concatMap(doTodoAsync)
I would use concatMap as it feels more idiomatic.
UPDATE: DEMO
How about some recursion?
First create a recursive function and call it recursiveDoToDo:
const recursiveDoToDo = (currentTodo, index) =>
Observable
.fromPromise(doTodoAsync(currentTodo))
.map(resolved => ({resolved, index}));
The code above simply wraps your doTodoAsync into an Observable, and then we map the results to return the resolved promise and the index of the array, for recursion use later.
Next, we will recursively call the recursiveDoToDo with the .expand() operator.
recursiveDoToDo(worktodo[0], 0)
.expand(res => recursiveDoToDo(worktodo[res.index + 1], res.index + 1))
.take(worktodo.length)
All you need to do for your recursion is just to increment the index by 1. Because .expand() will recursively run forever, the .take() operator is there to tell the observable when to end the stream, which is the length of your worktodo.
Now you can simply subscribe to it:
recursion.subscribe(x => console.log(x));
Here is the working JS Bin
In my code I have conditional tasks, which all return a promise. I need the tasks to run in sequence.
My current implementation looks something like this:
var chain = [];
if (/* some condition for task A */) {
chain.push(function(doContinue){
taskA().then(doContinue);
});
}
if (/* some condition for task B */) {
chain.push(function(doContinue){
taskB().then(doContinue);
});
}
if (/* some condition for task C */) {
chain.push(function(doContinue){
taskC().then(doContinue);
});
}
var processChain = function () {
if (chain.length) {
chain.shift()(processChain);
} else {
console.log("all tasks done");
}
};
processChain();
This works fine, but initially I was looking for a way to create the chain using only Promises and chaining all functions using .then, but I wasn't able to get a working solution.
If there's a cleaner way using only Promises and chains of then calls, then I'd love to see an example.
One possible approach:
var promiseChain = Promise.resolve();
if (shouldAddA) promiseChain = promiseChain.then(taskA);
if (shouldAddB) promiseChain = promiseChain.then(taskB);
if (shouldAddC) promiseChain = promiseChain.then(taskC);
return promiseChain;
Another one:
return Promise.resolve()
.then(shouldAddA && taskA)
.then(shouldAddB && taskB)
.then(shouldAddC && taskC);
You can use the new async/await syntax
async function foo () {
let a = await taskA()
if (a > 5) return a // some condition, value
let b = await taskB()
if (b === 0) return [a,b] // some condition, value
let c = await taskC()
if (c < 0) return "c is negative" // some condition, value
return "otherwise this"
}
foo().then(result => console.log(result))
What's nice about this is – aside from the code being very flat and readable (imo) – values a, b, and c are all available in the same scope. This means your conditions and return values can depend on any combination of your tasks' promised values.