Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AsyncEither example #16

Closed
Djordjenp opened this issue Feb 7, 2022 · 9 comments
Closed

AsyncEither example #16

Djordjenp opened this issue Feb 7, 2022 · 9 comments

Comments

@Djordjenp
Copy link

Hello Kyle,

I am having trouble understanding and using AsyncEither, can you give some kind of example of usage?

@getify
Copy link
Owner

getify commented Feb 7, 2022

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 catchable 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!

@Djordjenp
Copy link
Author

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)

@getify
Copy link
Owner

getify commented Feb 7, 2022

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.

@getify
Copy link
Owner

getify commented Feb 7, 2022

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.

@Djordjenp
Copy link
Author

Yeah I am using it of npm probably still isn't fixed, I thought I was doing something wrong. Ty for your help

@Djordjenp
Copy link
Author

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

@Djordjenp Djordjenp reopened this Feb 8, 2022
@getify
Copy link
Owner

getify commented Feb 8, 2022

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

@Djordjenp
Copy link
Author

Yeah that works,
And one final question, how can we throw an Error in IO chaining, for instance if "response.ok" is false.

@getify
Copy link
Owner

getify commented Feb 8, 2022

Just use throw inside any IO method (map(..), chain(..), etc), and that will bubble out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants