Closed MalQaniss closed 2 years ago
Some thoughts (I'll probably string these out in several comments)...
const sendHttp = url => IO.of(fetch(url))
This feels right, but it actually isn't. The reason is, IO.of(..)
is not deferring the fetch(..)
... the fetch(..)
is being performed, and the promise for its result is being put inside an IO
instance. The whole point of IO
is for side-effect operations to be lazy/deferred. Rule of thumb: don't use IO.of(..)
to encapsulate a value that comes from a side effect operation, unless you are sure you're already in the context of an IO.
That line should be:
const sendHttp = url => IO(() => fetch(url))
That small difference means that when sendHttp(..)
is called, with a url
, what comes back is an IO that will perform fetch(..)
when told to do so, but where fetch(..)
has not happened yet.
const checkResponse = response => response.ok ? IO.of(response) : IO(() => {throw "Response Error"})
That line is OK, but I'd prefer doing it this way:
const checkResponse = response => IO(() => {
if (!response.ok) throw "Response Error";
return response;
});
const getJson = response => IO.of(response.json())
Ditto to my first comment:
const getJson = response => IO(() => response.json())
const checkPopulation = data => data[0].population > 50_000_000 ? IO.of(data[0]) : IO(() => {throw 'Not Enough Population';})
Ditto to my second comment:
const checkPopulation = data => IO.of(() => {
if (data[0].population <= 50_000_000) throw 'Not Enough Population';
return data[0];
});
const fetchCapital = pipe(
IO.of,
chain(sendHttp),
chain(checkResponse),
chain(getJson),
chain(checkPopulation),
map(getCapital),
)
Monio just recently (in v0.50.0) added a .pipe(..)
helper (as a sub-method on methods like chain(..)
and map(..)
) that cleans this up nicely:
const fetchCapital = url => (
sendHttp(url)
.chain.pipe(
checkResponse,
getJson,
checkPopulation
)
.map(getCapital)
);
const text = await
fetchCapital('https://restcountries.com/v3.1/name/germany').run()
.catch(err => console.log("ERROR!!!"));
setBodyText(text).run()
That works, but I'd do it this way:
function main*() {
try {
const text = yield fetchCapital('https://restcountries.com/v3.1/name/germany');
yield setBodyText(text);
}
catch (err) {
console.log("ERROR!!!",err);
}
}
IO.do(main).run();
Side note: the console.log(..)
statement is, itself, a side-effect. Since we're going to the trouble to put all our side-effects in IO
s, in my opinion that should be no different.
catch (err) {
yield IO(() => console.log("ERROR!!!!",err));
}
I know that probably feels unnecessary, but it's a question of discipline. We don't want to get lax in our habits and start cheating the way IO
is supposed to work.
BTW, Monio's IOHelper
module ships with a log(..)
helper for this purpose, to make it a little easier to adhere. So you if you've imported log
from IOHelpers
:
catch (err) {
yield log("ERROR!!!!",err);
}
So... to bring it all together:
const prop = propName => obj => obj[propName];
const sendHttp = url => IO(() => fetch(url));
const checkResponse = response => IO(() => {
if (!response.ok) throw "Response Error";
return response;
});
const getJson = response => IO(() => response.json());
const checkPopulation = data => IO.of(() => {
if (data[0].population <= 50_000_000) throw 'Not Enough Population';
return data[0];
});
const fetchCapital = url => (
sendHttp(url)
.chain.pipe(
checkResponse,
getJson,
checkPopulation
)
.map(prop('capital'))
);
function main*() {
try {
const text = yield fetchCapital('https://restcountries.com/v3.1/name/germany');
yield setBodyText(text);
}
catch (err) {
yield log("ERROR!!!",err);
}
}
IO.do(main).run();
I got a lot of insights, thank you for your help.
I used IO monad in combination with Ramda, it all works as expected but I just wanna get your opinion should have I used something differently to Improve my using of IO in this situation: