executing nested 'while' loops within promises - javascript

I am trying to create some nested 'while' loops within my promises. I start to run into trouble in my innerFunc when I am executing log.asyncCall().then(...) my IDE is telling me that right away, the arrow function will jump straight to the .then() containing return innerFunc() without even going into the if statement. I think the race conditions are not executing in the order I want them to.
Summary of what I am trying to achieve:
According to outerFunc I want to perform innerFunc this 3 times.
What innerFunc does call .asyncCall() over and over again as long as log is NOT null. If innerFunc cannot produce log, exit the 'while' loop.
I'm also having trouble finding a good way to debug promises. I am currently using the debug feature with WebStorm.
var i = 0
const outerFunc = () => {
return new Promise(resolve => {
if (i < 3) {
i++
const innerFunc = () => {
return new Promise(resolve => {
return log.asyncCall().then(lg => {
if (lg) {
// some logic
} else {
resolve()
}
}).then(() => {
// continue iterating innerFunc()
return innerFunc();
});
})
};
// first iteration innerFunc()
return innerFunc().then(() => {
// once innerFunc() is complete, reiterate outerFunc()
return outerFunc();
});
} else {
resolve();
}
});
}

You are using a Promise anti-pattern, since you're wrapping a promise in another promise.
I would strongly advise taking innerFunc out of the inner scope, and rewriting it thus:
const doLogging = () => {
return log.asyncCall().then(result => {
if (result) {
// do stuff
...
return doLogging();
}
});
}
There's no need for an else branch - the inner arrow function will implicitly return undefined which will then be wrapped by .then into a Promise.
You then want to run this count times:
const repeatPromise = (f, count) => {
if (count > 0) {
return f().then(() => repeatPromise(f, count - 1));
} else {
return Promise.resolve();
}
}
const outerFunc = () => repeatPromise(doLogging, 3);
In this function the else branch is necessary to ensure that the final result is always a Promise.
(NB: as written the repeatPromise function won't work properly if the passed function doesn't return a Promise)

Related

Javascript: Recursive function with promise and resolve when count is reached

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.

Recursive function doesn't work within Promise

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 => {
// …
});

Use closure to purify a function which builds an object through recursion — JavaScript

I've made a promise based function which crawls up a hierarchy until it reaches the top, and resolves with an object containing the structure. My only gripe with the code is that I modify variables outside the function body, meaning that it is not a pure function. I've looked into JavaScript closures, and I fully grasp trivial uses of them. But I'm struggling to figure out how/if they can help make my function pure. My attempts at making a closure so far have only overwritten the variables, not modified them. Here is the code in question using global variables:
/* I want to move these variables inside function body to purify 'getPriorRows'*/
let priorRows = {}, level = 0;
const getPriorRows = id => new Promise(resolve => {
fetch(`/api/org/${id}`).then(result => {
/* global varaiables are modified here */
priorRows[level++] = result;
if (result.parentID) resolve(getPriorRows(result.parentID));
else resolve(priorRows);
});
});
getPriorRows('123432').then(result => console.log(result));
Any input on the matter is greatly appreciated.
Pass the values as arguments:
function getPriorRows(id, priorRows = {}, level = 0) {
return fetch(`/api/org/${id}`).then(result => {
/* global varaiables are modified here */
priorRows[level] = result;
if (result.parentID) return getPriorRows(result.parentID, priorRows, level+1);
else return priorRows;
});
}
getPriorRows('123432').then(result => console.log(result));
You can use either default parameters or a wrapper function, you don't even need a closure:
function getAll(id) { return getPriorRows(id, {}, 0); }
Also the I removed the Promise constructor antipattern.
You should be able to enclose the entire function and its "external" variables in a new function:
function getPriorRows(id) {
let priorRows = {}, level = 0;
const getNext = id => new Promise(
...
);
return getNext(id);
}
That said, your creation of an explicit new Promise in each iteration is a Promise anti-pattern:
function getPriorRows(id) {
let priorRows = {}, level = 0;
const getNext = id => fetch(`/api/org/${id}`).then(result => {
priorRows[level++] = result
if (result.parentID) {
return getNext(result.parentID));
} else {
return priorRows;
}
});
return getNext(id);
}
Either way, the advantage of wrapping the state like this is that you could now have multiple calls to getPriorRows proceeding in parallel without interfering with each other.
EDIT second code edited to fix a copy&paste error with the recursion - you must call the inner function recursively, not the outer one.

Create a function that no matter how many times invoked run only when first async call finishes?

suppose I've a function fetch(id).
I would be calling it arbitrarily at random times.
But I want every successive call to run only after previous call has finished.
say the async task takes 4 seconds, and i call fetch 3 times. then total time should be 12 seconds.
I could make a array and at every call set promise to go through next in line.
but what are some ways to accomplish this.
I think I got it
//First my example function which could be anything but should return promise which would be queued
function example(n) {
return new Promise((res, rej) => {
setTimeout(()=> {
console.log(n);
res();
}, 1000);
});
}
//now solution
function debounce(func) {
let p = Promise.resolve();
return function(x){
p = p.then(() => func(x));
}
}
//usage
d = debounce(example);
d(1);d(2);d(3);d(4);d(5);d(6);d(1);
You can chain Promises without an array, just store a pointer to the last Promise
// async payload function
// returns a promise
function f(x) {
console.log(`call f(${x})`);
return new Promise((resolve) => {
setTimeout(() => {
console.log(`resolve f(${x})`);
resolve();
}, 2000);
});
}
// wrapper to call function `f` sequentially
// stores pointer to the last Promise in closure
const g = (function(){
let lastPromise = Promise.resolve();
return function(arg){
lastPromise = lastPromise.then(() => f(arg));
}
})();
// generate random calls of function function `g`
for (let i = 0; i < 5; i++) {
setTimeout(() => g(i), Math.random() * 100);
}
I guess you can use async.io library.
Or, each time you want to call your function, push the function itself in an array. When the previous function has finished, check if there is still some functions to call in the array.

Avoid function declaration in for loops for promises

Eslint warns me not to define functions inside of for loops and I know it does so because it would define that function for every iteration which is obviously bad. However in my case I am not sure how I could rewrite the code so that this is not necessary anymore.
function refreshProfiles(job, done) {
let isFinished = false
for (let i = 0; i < 100; i++) {
// Get PlayerProfile promise and snapshot these
const p = PlayerProfile.findOneAndUpdate(filter, updateDoc).then((profile) => {
if (!profile) {
isFinished = true
}
return refreshPlayerProfileIntoHistory(profile)
}).catch((err) => {
// Error handling
})
promiseArray.push(p)
}
return Promise.all(promiseArray).then(() => {
// Recursive function call if we are not finished yet
if (isFinished) {
done()
} else {
// Recursive function call
refreshProfiles()
}
})
}
TL;DR what the code does: It should stop with recursive function calls once it can't find profiles anymore.
Question:
How can I avoid defining the function in a for loop for this specific case where I would need to access a variable (the isFinished bool) which lives outside of the function I am defining in the loop?
Move the isFinished flag and the function out of refreshProfiles and into the parent closure (so that both functions have access to it). Be sure to reset isFinished to false whenever the function is first called externally.

Categories