I am new to functional programming.
As I heard many times, functional programming could help with easier maintenance. So, I would like to see if it can help with this problem, or actually I may need to look for other solution. If other solution is needed, sorry for this misleading topic.
In my react project,
I have two states, one is Projection, other is called StressProjections
I have a function that will invoke two Api call, once the api return come back. It will update both state
Below is my code.
//call API1
const simulateionCall = new Promise((resolve, reject) => {
axios.post <
IProjectionResponse >
(`/api1`, normalProjectionRequest)
.then((data) => {
resolve(data);
})
.catch((err) => {
reject(err);
});
});
//call API12
const stressCall = new Promise((resolve, reject) => {
axios.post <
IStressProjectionResponse >
(`/api2`, stressProjectionRequest)
.then((data) => {
resolve(data);
})
.catch((err) => {
reject(err);
});
});
Promise.allSettled([simulateionCall, stressCall]).then((vals) => {
console.log(vals);
vals.forEach((val, index) => {
if (val.status === "rejected") {
if (index === 0) {
//ERROR handling api1
props.setProjection(resMock.results);
} else {
//ERROR handling api22
props.setStressProjections(res.results[0].scenarios);
}
} else {
if (index === 0) {
//Set content for api 1
// @ts-ignore
props.setProjection(val.value.data.result);
} else {
//Set content for api 2
// @ts-ignore
props.setStressProjections(val.value.data.results);
}
}
});
});
Question:
In my allsettle function, as you can see I need to check with the index before manually to assign which props.set function and which error handling should be call.
First 1, it is not readable, as some may wonder why we use this set function when it is in index 0.
Second2, it is hard in maintenance. When there are more and more Api call is included, my code would be full of conditional if (index === ...) , it increase the diffculty in reading
Hope I can see a new technique or functional way to cope with above two issues.
Adding handler as part of the resolve helps avoid the if conditions.
const mockAxiosPost = (url, body) => {
return new Promise((resolve) => {
setTimeout(() => resolve(body), body.key);
});
};
const normalProjectionRequest = {
key: 1000,
};
const stressProjectionRequest = {
key: 3000,
};
//call API1
const simulateionCallHandler = console.log;
const simulateionCall = new Promise((resolve, reject) => {
mockAxiosPost(`/api1`, normalProjectionRequest)
.then((data) => {
resolve({
data: data,
handler: simulateionCallHandler,
});
})
.catch((err) => {
reject(err);
});
});
//call API12
const stressCallHandler = console.log;
const stressCall = new Promise((resolve, reject) => {
mockAxiosPost(`/api2`, stressProjectionRequest)
.then((data) => {
resolve({
data: data,
handler: stressCallHandler,
});
})
.catch((err) => {
reject(err);
});
});
const props = {
setProjection: console.log,
setStressProjections: console.log,
};
Promise.allSettled([simulateionCall, stressCall]).then((vals) => {
vals.forEach((val, index) => {
console.log(vals);
if (val.status === 'rejected') {
if (index === 0) {
//ERROR handling api1
props.setProjection(resMock.results);
} else {
//ERROR handling api22
props.setStressProjections(res.results[0].scenarios);
}
} else {
val.value.handler(val.value.data);
}
});
});
avoid the explicit promise construction antipattern
const simulateionCall = new Promise((resolve, reject) => {
axios.post <
IProjectionResponse >
(`/api1`, normalProjectionRequest)
.then((data) => {
resolve(data);
})
.catch((err) => {
reject(err);
});
});
is the same as -
const simulateionCall =
axios.post<IProjectionResponse>("/api1", normalProjectionRequest)
you make the same mistake with stressCall.
don't use Promise.allSettled
You are seeing the obvious disadvantages of using Promise.allSettled, but there's no reason to use it in the first place. Your promises can run in separate "threads" to avoid tangling them together and having to keep track of their indexes.
axios
.post<IProjectionResponse>("/api1", normalProjectionRequest)
.then(data => props.setProjection(data.result))
.catch(console.error) // or something else
axios
.post<IStressProjectionResponse>("/api2", stressProjectionRequest)
.then(data => props.setStressProjections(data.results))
.catch(console.error) // or something else
react
You tagged this with react. If you want these two posts to happen when the user presses a button -
const onClick = (event) => {
axios
.post<IProjectionResponse>("/api1", normalProjectionRequest)
.then(data => props.setProjection(data.result))
.catch(console.error) // or something else
axios
.post<IStressProjectionResponse>("/api2", stressProjectionRequest)
.then(data => props.setStressProjections(data.results))
.catch(console.error) // or something else
}
return <>
...
<button type="button" onClick={onClick}>Submit</button>
</>
If you want to re-run these posts requests each time the normal and stress projection requests change, use useEffect with normalProjectionRequest and stressProjectionRequest as dependencies of the effect -
useEffect(() => {
axios
.post<IProjectionResponse>("/api1", normalProjectionRequest)
.then(data => props.setProjection(data.result))
.catch(console.error)
axios
.post<IStressProjectionResponse>("/api2", stressProjectionRequest)
.then(data => props.setStressProjections(data.results))
.catch(console.error)
}, [normalProjectionRequest, stressProjectionRequest])