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

Adds explaination of flatMap semantics #86

Closed
wants to merge 15 commits into from
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,10 @@ We propose the following operators in addition to the `Observable` interface:
- `finally()`
- Like `Promise.finally()`, it takes a callback which gets fired after the
observable completes in any way (`complete()`/`error()`)
- `flatMap()`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should appear both in this "exceptional" list, and in the list below about iterator helpers. Seems like it should just exist in the list of iterator helpers, with a note that the semantics are a little different (as you provided below).


- Similar to `Iterator.prototype.flatMap`, however, because the types are different,
there are some semantics to note.

Versions of the above are often present in userland implementations of
observables as they are useful for observable-specific reasons, but in addition
Expand Down Expand Up @@ -554,6 +558,55 @@ Promises whose scheduling differs from that of Observables, which sometimes
means event handlers that call `e.preventDefault()` will run too late. See the
[Concerns](#concerns) section which goes into more detail.

### flatMap semantics

`flatMap` generally behaves like `Iterator.prototype.flatMap`, however, since it's push-based,
there can be a temporal element. Given that, it behaves much like RxJS's `concatMap`, which is
one of the most useful operators from the library.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which is one of the most useful operators from the library.

I think we can remove this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping


At a high-level, it subscribes to the source, and then maps to, and emits values from "inner
observables", one at a time, ensuring they're subscribed to in sequence.

Given the following example:

```ts
const result = source.flatMap((value) => getNextInnerObservable(value));
```

- When you subscribe to `result`, it subscribes to `source` immediately.
- Let there be a `queue` of values that is empty.
- Let there be an `innerSignal` that is either `undefined` or an `AbortSignal`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which is it? Does this mean to represent the signal that's passed into the subscription of result? (Clarifying that might be good)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct. I'll add something.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I do explain that below: Pass the innerSignal to the subscribe for the inner observable.

- Let there be an `isSourceComplete` that is `false`.
benlesh marked this conversation as resolved.
Show resolved Hide resolved
- When the `source` emits a value:
- If `innerSignal` is `undefined`
- Call the mapping function with the the value.
benlesh marked this conversation as resolved.
Show resolved Hide resolved
- Then pass the return value of the mapping function to `Observable.from()` to convert it to
"inner observable" if it's not already.
- Then create an `AbortSignal` that follows the subscriber's and set `innerSignal`.
benlesh marked this conversation as resolved.
Show resolved Hide resolved
- pass the `innerSignal` to the subscribe for the inner observable.
benlesh marked this conversation as resolved.
Show resolved Hide resolved
- Forward all values emitted by the inner observable to the `result` observer.
- If the inner observable completes
- If there are values in the `queue`
- take the first one from the `queue` and return the the mapping step.
benlesh marked this conversation as resolved.
Show resolved Hide resolved
- If the `queue` is empty
- If `isSourceComplete` is `true`
- Complete `result`.
- If `isSourceComplete` is `false`
- Wait
- If the inner observable errors
- Forward the error to `result`.
- If `innerSignal` is `AbortSignal`
benlesh marked this conversation as resolved.
Show resolved Hide resolved
- Add the value to the `queue` and wait.
- If the `source` completes:
- If `innerSignal` is `undefined`
- Complete `result`.
- If `innerSignal` is `AbortSignal`
- set `isSourceComplete` to `true`.
benlesh marked this conversation as resolved.
Show resolved Hide resolved
- If the `source` errors:
- Forward the error to `result`.
- If the user aborts the signal passed to the subscription of `result`
- Abort any `innerSignal` that exists, and terminate subscription.

## Background & landscape

To illustrate how Observables fit into the current landscape of other reactive
Expand Down