Refactoring a big function into functions within the function? - javascript

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.

Related

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

'await' has no effect on the type of this expression when using async/await inside nested functions

Basically Visual Studio Code gives me the note 'await' has no effect on the type of this expression. but I am note able to track the actual problem down. Of course my example here is a bit nested and therefor a bit more complex than it maybe has to be. It all starts with an array where I want to check it a value is valid. If on value fails the test, the .every function should end.
myList.every(function (value) {
if(value == null) return false;
//other stuff for preparation
return _handleValue(value);
});
value is a complex datatype so the _handleValue function is splitted into different subfunctions. Some of the function are using promises some (atm) don't. Please ignore the functions in the if block because the code is simplified and this is not causing the problem.
async function _handleValue(value) {
if (somePreChecks(value)) {
return false;
}
else if (valueIsType(value)) {
return await _someHandlerwithPromise(value); //<-- Using await here.
} else if (valueIsOtherType(value)) {
return _someOtherHandlerWithoutPromise(value);
}
}
The function _someHandlerWithPromise has some call which are using callback. Anyway this should not be a problem as long as I call reject().
function _someHandlerWithPromise(value) {
return new Promise(resolve => {
foo.acceptValue(value, function (data) {
_updateSystem(data, function (err, data) {
if(err){
//Do something
}
resolve(true);
});
});
});
}
Actually this worked pretty well elsewhere where I do not use the await function. But because the .every is not able to handle asynchronous methods, I am forced to use await.

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.

Go through undetermined number of Promises sequentially

I have this class that has method next returning a Promise.
class PromiseGenerator {
constructor() {
this.limit = 100;
this.counter = 0;
}
next() {
this.counter++;
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(this.counter <= this.limit ? this.counter : false);
}, 500);
});
}
}
Though example shows 100 it could return unlimited number of promises.
I need to execute all the promises in sequential order.
How can I achieve it?
Only solution I came up so far is recursive:
const source = new PromiseGenerator();
(function loop() {
source.next().then(counter => {
if (counter) {
console.log(counter);
loop();
} else {
console.log('Done');
}
});
})();
As I understand Node at the moment does not optimize tail calls and it may lead to stack growth.
Is there a better way of doing this?
if some Promise library has it it will work but it would be nice to understand how to achieve it without library as well.
Update 1: Sorry I didn't make it clear right away: I am not in control of PromiseGenerator class, it is something I can use but cannot change. So the question is how to handle this situation.
Update 2: I went with #eikooc solution: Without generators but just with async/await. See example below.
Thanks!
Generators are a perfect match for this. Construct a generator with the function* keyword:
function* promiseGenerator() {
while(!someCondition) {
yield new Promise((resolve, reject) => {})
}
}
And then call it with:
const source = promiseGenerator()
source.next()
This will continue to give you new values. The return looks like this {value: Promise, done: false} until it is finished.
When the generator finishes the done value will change to true
If you want to keep using the class and just want a loop. You can also combine your class with a async function:
async function loop() {
const source = new PromiseGenerator()
while (true) {
const result = await source.next()
if (result) {
console.log(result)
} else {
console.log('done')
break
}
}
}
loop()

How to wait 'for' loop for returning

I know it is a simple, dumb question but it's been two days since I'm stuck on it.
Consider there is a function, managing creation of object type Course from some object type UnsafeCourse, like so:
Class Schedule {
constructor() {
this.courses = [];
}
sectionNeedsCourse(unsafeCourse) {
this.courses.forEach(function (course, index, array) {
if (course.couldBeRepresentedBy(unsafeCourse)) {
return course;
}
}
return new Course(unsafeCourse, [some additional parameters...]);
}
}
As things in node.js works asynchronously, for loop is gone-through.
I tried different approaches with Promises, didn't work.
sectionNeedsCourse(unsafeCourse) { //-> Promise
return new Promise (function (resolve) {
new Promise(function (resolve) {
this.courses.forEach(function (course, index, array) {
if (course.couldBeRepresentedBy(unsafeCourse)) {
resolve(eachCourse);
}
});
resolve(null);
}).then(function (result) {
console.log(result);
if (result != null) {
addCourse(unsafeCourse).then(function (result) {
resolve(result);
});
} else {
resolve(result);
}
});
});
}
Also, is it a good practice to use multiple Promises in same function?
Does it make heavy overhead?
I can't see any async method in your first example. Array.forEach is not a async function.
You just return the course inside of the callback of forEach, but you must return it directly in sectionNeedsCourse:
sectionNeedsCourse(unsafeCourse) {
var course = this.courses.find(function(course){
course.couldBeRepresentedBy(unsafeCourse) ? course : false;
});
return course ? course : new Course(unsafeCourse, []);
}
As you can see, i also use find instead of forEach, because this is the logic you need here.
As requested here the same example a little bit shorter with an Arrow function:
sectionNeedsCourse(unsafeCourse) {
var course = this.courses.find(course => {
course.couldBeRepresentedBy(unsafeCourse) ? course : false;
});
return course ? course : new Course(unsafeCourse, []);
}

Categories