Recursive function doesn't work within Promise - javascript

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

Related

Refactoring a big function into functions within the function?

I have two functions that are not so big, and not so complex (maybe because I wrote them they don't seem so at the moment) and I have tried refactoring them (successfully), however, would that be considered overdoing it:
Original function:
setInterval(
() => {
robots.forEach((robot) => {
ping.promise.probe(robot.IP).then(async (resp) => {
if (resp.alive) {
for (let dataType of TypesOfDataToGet) {
await dataType.Set(robot);
}
} else {
console.log(`${robot.Name.EN} is offline!`);
}
});
});
}, 400);
Into:
function iterateRobots(robots, doSomethingOnRobots) {
robots.forEach((robot) => {
doSomethingOnRobots(robot);
});
}
function pingRobots(robot) {
ping.promise.probe(robot.IP).then(async (resp) => {
getRobotDataIfRobotAlive(resp, robot);
});
}
async function getRobotDataIfRobotAlive(resp, robot) {
if (resp.alive) {
for (let dataType of TypesOfDataToGet) {
await dataType.Get(robot);
}
} else {
console.log(`${robot.Name.EN} is offline!`);
}
}
setInterval(() => {
iterateRobots(robots, pingRobots);
}, 400);
Original second function:
robots.forEach((robot) => {
robot.Events.forEach((event) => {
socket.on(event, (data) => {
let eventStartIndex = event.lastIndexOf("-");
let eventDataType = event.substring(eventStartIndex + 1);
for (let currentDataType of TypesOfDataToSet) {
if (currentDataType.DataType === eventDataType.toUpperCase()) {
currentDataType.Set(robot, data);
break;
}
}
});
});
});
Into:
function iterateRobots(robots, doSomethingOnRobots) {
robots.forEach((robot) => {
doSomethingOnRobots(robot);
});
}
function iterateEvents(robot) {
robot.Events.forEach((event) => {
sendDataBasedOnEventType(robot, event)
});
}
function sendDataBasedOnEventType(robot, event) {
socket.on(event, (data) => {
let eventStartIndex = event.lastIndexOf("-");
let eventDataType = event.substring(eventStartIndex + 1);
for (let currentDataType of TypesOfDataToSet) {
if (currentDataType.DataType === eventDataType.toUpperCase()) {
currentDataType.Set(robot, data);
break;
}
}
});
}
iterateRobots(robots, iterateEvents);
Now obviously the first thing is that, this is much more code when refactored like this, and looking at the before and after of the functions while writing them here, the original approach is more readable, but I have them arranged one after another in order in my code and their internal code is "minimized", so I just see the names of the functions in their logical order.
So my question is, would this be considered like something that I have to do?
If not, what criteria should a function meet for me to have to do something like this?
And is that even the right way to do it?
One first tip, is to leverage that functions in JS are first-class citizens, so this one:
robots.forEach((robot) => {
doSomethingOnRobots(robot)
})
can be written as:
robots.forEach(doSomethingOnRobots)
Something that might make the refactoring feel awkward is that some of these extracted functions need robot as a parameter, which in the original are accessed via closure.
First Example
You can look for ways to split the function in a way that preserves this closure. Since you used async in the example, you could leverage it for the first promise as well:
async function pingRobot (robot) {
const resp = await ping.promise.probe(robot.IP)
if (!resp.alive) return console.log(`${robot.Name.EN} is offline!`)
for (let dataType of TypesOfDataToGet) {
await dataType.Set(robot)
}
}
setInterval(() => robots.forEach(pingRobot), 400)
By separating the core logic (checking the robot status) from the timer and the iteration, we make the pingRobot function easier to test.
Second Example
Regarding the second function, it might desirable to replace the iteration with a structure that allows you to obtain a type from the event DataType. An example using keyBy (which you can implement manually if needed):
const typesByDataType = keyBy(TypesOfDataToSet, 'DataType')
function onRobotEvent ({ robot, event, data }) {
const eventStartIndex = event.lastIndexOf("-")
const eventDataType = event.substring(eventStartIndex + 1).toUpperCase()
const eventType = typesByDataType[eventDataType]
if (eventType) eventType.Set(robot, data)
}
robots.forEach(robot => {
robot.Events.forEach(event => {
socket.on(event, data => {
onRobotEvent({ robot, event, data })
})
})
})
The main trick is again to see which closures you were leveraging in the original code, and preserve them to avoid verbosity. Although it might be a bit longer, onRobotEvent has become easier to reason about, and test in isolation.
IMHO the criteria are testability and readability.
First means the function can be easily tested. If the number of params will increase, the size of unit test of the function will also increase. If your function do something else (not one exact operation) your test function will have to test it also. All the control structures of your function forces you to test them.
So your function is small enough if it can be easily and completely be tested by unit testing.

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.

How to simplify setTimeout in ReactJS

There was a typewriting animation in pure javaScript which has been converted to ReactJS. The setTimeout functions do not look clean and do not adhere to best practices according to ReactJS standard.
For example animationManager()
animationManager = () => {
this.rafRef = requestAnimationFrame(time => {
const typingData = this.props.data;
this.typeEffect(time, typingData[this.index], () => {
this.timeoutRef = setTimeout(() => {
this.rafRef = requestAnimationFrame(time => {
this.deleteEffect(time, () => {
this.timeoutRef = setTimeout(() => {
this.index =
this.index === typingData.length - 1 ? 0 : this.index + 1;
this.animationManager();
}, this.props.pauseBeforeRestarting);
});
});
}, this.props.pauseBeforeDeleting);
});
});
};
Is it possible to make it more clean with all these setTimout ?
Complete code 👉 https://codesandbox.io/s/qk4591q1kw
Yes, you can actually create functions that acts like a timer: it returns a promise that is resolved when the time runs out, something like this:
timer = (duration) => {
return new Promise(resolve => {
window.setTimeout(resolve, duration);
});
}
Similarly, you can do the same for requestAnimationFrame. The trick is to use ES6 spread operator so that you can pass arbitrary number of arguments into the callback to be invoked:
animationFrame = (callback, ...args) => {
return new Promise(resolve => {
window.requestAnimationFrame(time => {
callback(time, ...args);
});
})
}
Since you are using ES6, you can then use async functions to wait for the timer to complete, before moving on to execute the next line of code. If we break down your animationManager() code, it can be seen as following:
You want to start with typingEffect
Once typingEffect is completed, you want to trigger deleteEffect
In this case, we can refactor your code as such:
animationManager = () => {
const deleteFunc = (time, typingData) => {
this.deleteEffect(time, async () => {
await this.timer(this.props.pauseBeforeRestarting);
this.index = this.index === typingData.length - 1 ? 0 : this.index + 1;
this.animationManager();
});
};
const typeFunc = (time) => {
const typingData = this.props.data;
this.typeEffect(time, typingData[this.index], async () => {
await this.timer(this.props.pauseBeforeDeleting);
await this.animationFrame(deleteFunc, typingData);
})
};
this.animationFrame(typeFunc);
};
I have forked your example to provide a proof-of-concept of the slightly refactored code: https://codesandbox.io/s/308kxjzwrq
The common practice is to use Promises for that. You can create helper Promise which will use requestAnimationFrame, and make your flow flat and "thenable", by adding success callbacks onResolve.

Access data from promise calling another promise

I have a promise calling another promise but I don't know how to access the variable memberContractInfo where I am trying to store all of the promises. In the below code I have 2 questions labeled QUESTION 1 and QUESTION 2.
export function sendRequestAndLoadResponseForAllMemberContractInfo() {
return function sendRequestAndLoadResponseForAllMemberContractInfoThunk(dispatch) {
dispatch(getRequestsAction());
return returnPromiseWithAllMemberContracts()
.then(promiseWithAllMemberContracts => {
// Step 1) get all member ids in response
let contracts = promiseWithAllMemberContracts.response.contract;
let memberContractInfo = []; // <==== I want to store result of all 2nd promises here
for (let i in contracts) {
const memberID = contracts[i].member_id;
returnPromiseWithAllMemberInfo(memberID)
.then(secondAPICallResponse => {
// Step 2) make 2nd API call using memberIDs as parameter
memberContractInfo.push(secondAPICallResponse);
console.log('secondAPICallResponse = ', secondAPICallResponse);
if (memberContractInfo.length === 2) {
console.log('memberContractInfo.length = 2');
// QUESTION 1: I can access memberContractInfo here but I there must also be
// another place I can access it right?
}
})
}
console.log('memberContractInfo = ', memberContractInfo); // <== QUESTION 2: Why is this empty?
});
}
}
function returnPromiseWithAllMemberContracts() {
return fetchData('/api-proxy/contract/contract');
}
function returnPromiseWithAllMemberInfo(memberID) {
let servicePath = '/api-proxy/member?id='.concat(memberID);
console.log('fetchData(', servicePath);
return fetchData(servicePath);
}
You can access memberContractInfo anywhere inside they scope it is declared in then(promiseWithAllMemberContracts => {}.
memberContractInfo is empty in console.log('memberContractInfo = ', memberContractInfo); because you reach this statement before you actually resolved the promise.
As mentioned by #bergi you need to use Promise.all instead of loop.
Promise.all(contracts.map((contract) => {
return returnPromiseWithAllMemberInfo(contract.member_id);
})).then(values => {
// values has response from all the above API calls stored in it
return values;
}).then((memberContractInfo) => {
console.log(memberContractInfo.length);
// this will give you correct value.
})

While loop using bluebird promises

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

Categories