Closed Djordjenp closed 2 years ago
The best way to think about AsyncEither
is that it's like a Future monad, which is sorta like a Promise. With a Future/Promise, you have an asynchronous operation that, when it completes, will have one of two final states: success or failure.
The Either
monad models this sort of synchronous outcome, so AsyncEither
extends all the same concepts to asynchronicity.
As a simple example:
function request(url) {
return AsyncEither( fetch(url) )
.chain(resp => (
!resp.ok ?
AsyncEither.Left("Request failed.") :
AsyncEither.Right(resp.json())
));
}
// later
var records = await (
request("/some-api")
.map(data => data.records)
.fold(
(err) => { logError(err); return []; },
v => v
)
);
Like promises (and Maybe
and Either
monads), the subsequent chain(..)
and map(..)
calls get skipped if the AsyncEither
becomes an AsyncEither:Left
(which will just bubble forward the held exception message). Any promise rejection or uncaught JS exception will end up lifted to an AsyncEither:Left
. The final fold(..)
call extracts either the Left exception or the Right final success value. The whole chain operates as a promise and resolves with the final result, hence the single await
call.
I've included AsyncEither
because I think a monads library, for complete'ness sake if nothing else, needs a Future monad.
But in truth, I think all I/O (like a fetch(..)
) should be modeled in IO
instances, and that goes for pretty much any form of asynchrony (timers, animations, etc). Everything AsyncEither
can do, you can do with Monio's IO
, including even treating Either:Left
values as catch
able exceptions in IO.doEither(..)
routines. So I personally wouldn't use AsyncEither
, and would instead just use IO
(and Either
) together.
But you can think of AsyncEither
as a lighter-weight approach if you didn't want or care to use IO
. Or you can think of IO
as a superset of AsyncEither
-- essentially IO
is sorta a Task monad based on the Future (aka AsyncEither
) wired into it internally. You can just use normal JS promises with IO
and it opaquely transforms over them, without you needing to mess around with Future / AsyncEither
wrappings yourself.
Hope that's helpful!
Ty for you answer Kyle, I understand it now, I will definitely look at IO monad next. Btw I tried your example with AsyncEither:
function request(url) {
return AsyncEither(
fetch(url).then(resp => {
if (!resp.ok) throw "Request failed.";
return resp;
})
)
.chain(toJSON);
}
function toJSON(resp) {
return AsyncEither( resp.json() );
}
// later
const records = await (
request("https://swapi.dev/api/planets")
.map(data => data.count)
.fold(
(err) => { console.log(err); return []; },
v => v
)
);
console.log(records)
But it seems to report some internal error:
Uncaught ReferenceError: identity is not defined
at async-either.mjs:5:1348
at Object.fold2 [as fold] (either.mjs:5:724)
at _doChain (async-either.mjs:5:1265)
at handle (async-either.mjs:5:1432)
For posterity, request(..)
above could alternately have been implemented like this:
function request(url) {
return AsyncEither( fetch(url) )
.map(resp => {
if ( !resp.ok ) throw "Request failed.";
else return resp.json();
});
}
This style is a little more familiar/ergonomic to JS devs, but it relies on conveniences that AsyncEither
provides, which is that it's automatically lifting thrown exceptions into AsyncEither:Left
. I think the former version is a little more monadic-canonical.
But it seems to report some internal error:
Are you using Monio off npm, or from github? I'm not sure, but... I think this may be a bug that I've fixed in the code that's on github, but hasn't yet been released to npm. That release is coming shortly I believe.
Yeah I am using it of npm probably still isn't fixed, I thought I was doing something wrong. Ty for your help
Hey sorry to bother u again Kyle,
I used "IO.do" syntax and there I could use try/catch to catch an exception, but I don't know how i can catch exception when I am using more "functional" syntax. This is the example I tried:
const sendRequest = url => IO.of(fetch(url).catch(err => {
console.log("ERROR: " + err);
return err}));
const getJson = response => IO.of(response.json());
sendRequest('https://swapi.dev/api/WrongUrl')
.chain(getJson)
.map(x => console.log('SUCCESS: ' + x))
.run();
Try this:
const sendRequest = url => IO.of(fetch(url));
const getJson = response => IO.of(response.json());
sendRequest('https://swapi.dev/api/WrongUrl')
.chain(getJson)
.map(x => console.log('SUCCESS: ' + x))
.run()
// we get a promise back here, so we can call `catch(..)` on it
.catch(err => console.log("ERROR:",err));
Yeah that works, And one final question, how can we throw an Error in IO chaining, for instance if "response.ok" is false.
Just use throw
inside any IO method (map(..)
, chain(..)
, etc), and that will bubble out.
Hello Kyle,
I am having trouble understanding and using AsyncEither, can you give some kind of example of usage?