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

Do you think this Promise -> Stream conversion is a right alternative way? #194

Closed
gitusp opened this issue Feb 19, 2019 · 6 comments
Closed

Comments

@gitusp
Copy link

gitusp commented Feb 19, 2019

Hello, thank you for providing this beautiful project.
Although I'm not sure whether this question should go here, I'd really like to hear opinions from this community.


As discussed #35, it seems reasonable to convert a Promise into an either-like stream.
But I noticed that I usually take another approach to convert Promise.
Here I describe my approach:

// My promise conversion function.
const convertPromiseIntoStream = promise => {
  const s = flyd.stream({ state: "pending" });
  promise.then(
    value => {
      s({ state: "fulfilled", value });
      s.end(true);
    },
    reason => {
      s({ state: "rejected", reason });
      s.end(true);
    }
  );
  return s;
};

// Something that handles events.
const postMessage = flyd.stream();

// Something that calls API and returns a Promise
const callPostMessageAPI = (message) => new Promise(...);

// A stream that provides the current request status.
const requestState = switchLatest(
  postMessage.map(callPostMessageAPI).map(convertPromiseIntoStream)
);

// Extracts rejected reasons from a stream.
const toReasonStream = (stream) =>
  filter(s => s.state === "rejected", stream).map(s => s.reason)

// Error handling.
flyd.on(
  alert,
  toReasonStream(requestState).map(reason => reason.message)
);

The concept behind this conversion is that "a Promise must be mapped to one of these state, 'pending' | 'fulfilled' | 'rejected'", as described here.

The first difference between this approach and flyd.fromPromise is that the stream created with convertPromiseIntoStream emits "pending state" at first.
That makes it easy to know in what state a Promise is, and display some loading indicator to user.
The second difference is that the stream emits the second state, "fulfilled" | "rejected", after emitting "pending", so it is easy to filter values or reasons into a new stream and handle it.

Although I find this approach very efficient, especially building a client application, and think that it should be one of the standard Promise-Stream conversions, I'm wondering if the approach is over simplifying Promise concept, because I know nobody takes the approach and I do not have a deep knowledge in FRP.

I appreciate any feedbacks from you.
Thank you.

@StreetStrider
Copy link
Contributor

Looks pretty reasonable. I think Promise<R, E> → flyd$Stream<Either<R, E>> is a straightforward conversion and your idea follows it. There was a couple of tickets here in tracker related to your topic. Looks like this is current state of the things to convert.

You've introduced pending state. The minor change is if your widget cannot return to pending state you can just initialize widget in this state and eliminate pending from stream completely. In that case you'll end up with usual Either.

@StreetStrider
Copy link
Contributor

I've experimented with some dirty approach to this, where I pass raw Error objects in streams. Anything except Error is considered to be a data. It's just another way to implement Either. The main idea is you're not obliged to wrap all your data. You just pass data as usual, but in some cases Error can occur (in that case you still need combinators to extract data or error just like with Either).

@gitusp
Copy link
Author

gitusp commented Feb 20, 2019

@StreetStrider Thank you for your feedback.
Now I understand there's cases that the approach cannot apply as you mentioned.
I think whether the approach can apply depends on what I focus, the Promise's result or state.

I like the idea to pass raw Error objects in streams.
There must be varied ways to handle error with stream and each has slightly different meaning.
Although passing raw Error is almost the same with Either, it seems that it treats data and Error as rather close level things than Either - Either explicitly expresses which is "Right" while this approach does not.

I close this issue since my question is resolved.
Thank you.

@gitusp gitusp closed this as completed Feb 20, 2019
@StreetStrider
Copy link
Contributor

@gitusp I've recalled related discussions #171 #35 #164 you can take a look at them.
If any traction someday will occur, I expect it to occur in that tickets.

@nordfjord
Copy link
Collaborator

I'm of the opinion that this is something that should be solved in user land.

The fact is we all have different use cases for converting promises, sometimes we need a loader, sometimes we want to maintain order, sometimes we just want all promises to push to a result stream.

In my codebase I've used:

const promiseToEitherStream = p => {
  const s = stream();
  p.then(val => s(Right(val))).catch(err => s(Left(err)))
  return s
}

const promiseToResultStream = p => {
  const s = stream(Left('pending'))
  p.then(value => s(Right(value))).catch(error => s(Left(error)))
  return s
}

// usage
const result = stream(url)
  .map(makeRequest)
  .chain(promiseToResultStream)
  .map(cata({
    Left: R.ifElse(R.equals('pending'), renderLoader, renderError),
    Right: renderView
  }))

function cata(cata) {
  return monad => monad.cata(cata);
}

function renderLoader() {
  return <Loader />
}

function renderError(err) {
  return <Error error={err} />
}

function renderView(data) {
  // ...
}

And in some cases depending on whether ordering must be preserved I use either flyd.fromPromise or flyd.flattenPromise.

I don't think flyd should prescribe to it's users how to interact with promises.

@gitusp
Copy link
Author

gitusp commented Feb 27, 2019

I agree with you. promiseToEitherStream and promiseToResultStream are both necessary depending on use cases. There should be several ways to convert a promise to a stream.
As you showed, emitting pending as Left seems more semantic for me, since pending is meta information and the domain object is the promise's result.

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

3 participants