Skip to content

Commit

Permalink
Docs: Combining and merging operators
Browse files Browse the repository at this point in the history
  • Loading branch information
raquo committed Aug 14, 2024
1 parent 817912a commit adb9788
Showing 1 changed file with 62 additions and 0 deletions.
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ I created Airstream because I found existing solutions were not suitable for bui
* [Avoiding Glitches When Merging](#avoiding-glitches-when-merging)
* [Scheduling of Transactions](#scheduling-of-transactions)
* [Operators](#operators)
* [Combining Operators](#combining-operators)
* [Merging Operators](#merging-operators)
* [Distinction Operators](#distinction-operators)
* [N-arity Operators](#n-arity-operators)
* [Compose Changes](#compose-changes)
Expand Down Expand Up @@ -531,6 +533,8 @@ What sets EventBus apart from e.g. `EventStream.withObserver` is that you can al

You've probably noticed that `addSource` takes `owner` as an implicit param – this is for memory management purposes. You would typically pass a WriteBus to a child component if you want the child to send any events to the parent. Thus, we want `addSource` to be automatically undone when said child is discarded (see [Ownership](#ownership)), even if `writer.stream` is still being observed.

Note: if using Laminar, you can create an EventBus and send events into it with `source --> eventBus` – that way you don't need to manage owners manually, the parent element of this `-->` call will effectively be the owner.

An EventBus can have multiple sources simultaneously. In that case it will emit events from all of those sources in the order in which they come in. **EventBus always emits every event in a new [Transaction](#transactions).** Note that EventBus lets you create loops of Observables. It is up to you to make sure that a propagation of an event through such loops eventually terminates (via a proper `.filter(passes)` gate for example, or the implicit `==` equality filter in Signal).

You can manually remove a previously added source stream by calling `kill()` on the Subscription object returned by the addSource call.
Expand Down Expand Up @@ -1064,6 +1068,64 @@ Airstream offers standard observables operators like `map` / `filter` / `collect
Some of the more interesting / non-standard operators are documented below:


#### Combining operators

These operators get current / latest values from several observables at once.

##### combineWith

The standard `combineWith` operator emits updates that are the tuples of the latest available values. In that sense it is quite similar to the [combineLatest](https://reactivex.io/documentation/operators/combinelatest.html) RX operator. This is the canonical way to combine two observables in Airstream ([and not flatMap](#avoid-unnecessary-flatmap)).

For example, `signalA.combinewith(signalB)` emits the latest available `(A, B)` value whenever `signalA` or `signalB` emits. If both signals emit simultaneously, i.e. in the same transaction, then the combined signal will emit only once, avoiding the common [FRP glitch](#frp-glitches). See [Topological rank](#topological-rank).

For streams (`streamA.combinewith(streamB)`), the combined stream emits its first event when it has observed all of its parent streams to have emitted at least one event. Since it emits `(A, B)`, it needs to wait for both `A` and `B` to become available.

`combineWith` can only be used with either signals, or streams. You can't mix them. You can however convert e.g. your stream to a signal before handing it off to `signal.combineWith`, using stream operators like `toWeakSignal`, `startWith(initial)`, `scanLeft`, etc.

`combineWith` has several arity helpers. See [N-arity Operators](#n-arity-operators).
- You can combine more than two observables at once, e.g. stream1.combineWith(stream2, stream3, ...)`
- `combinieWith` auto-flattens nested tuples, i.e. `streamA.combineWith(streamB).combineWith(streamC)` will yield events of `(A, B, C)`, not the inconvenient `(A, B), C)`

`combineWith` has several other variations:
- `combineWithFn` lets you specify an alternative combining function instead of tupling.
- `EventStream.combine(stream1, stream2, ...)` and `Signal.combine(signal1, signal2, ...)` helpers.

#### withCurrentValueOf

This operator, defined for both signals and streams, lets you get read the current value of another signal, every time a certain observable emits an update. For example, `stream.withCurrentValueOf(signal)` will emit `(event, <currentSignalValue>)` whenever `stream` emits `event`. For convenience, you can also read the current value of `Var`-s this way, although for Var-s, you can always just call `.now()`.

See [Getting Signal's current value](#getting-signals-current-value).

You can read the values of multiple signals and/or vars at once: `observable.withCurrentValueOf(signal1, signal2, var3)`.

#### sample

This operator is exactly like `withCurrentValueOf`, but it discards the `event` itself. So, `stream.withCurrentValueOf(signal)` will emit `(<currentSignalValue>)` whenever `stream` emits an event. So the `stream` is basically acting as a timing / trigger for sampling other signals and/or vars (yes, you can sample multiple at the same time, just as with `withCurrentValueOf`.

See [Getting Signal's current value](#getting-signals-current-value).


#### Merging Operators

These operators re-emit events from each of their parent streams.

##### mergeWith

`stream1.mergewith(stream2, stream3, ...)` emits all of the events that `stream1`, `stream2`, `stream3`, etc. emit. This operator only accepts streams of the same event type, and returns a stream of that same type.

Aliases / helpers:
- `EventStream.merge(stream1, stream2, stream3, ...)`
- `EventStream.mergeSeq(seqOfStreams)`

See also:
- [Avoiding Glitches When Merging](#avoiding-glitches-when-merging)
- [mergeWith: loopy or flowy?](#merge-streams-special-case)

##### Merging dynamic sets of streams

`mergeWith` works for merging static, known-in-advance sets of streams, but if you want to merge a set of streams that varies over time, you can use [flatMapMerge](#flatmapmerge) or flattenMerge, [EventBus.addSource](#eventbus), or, in Laminar, you can create an EventBus and `-->` events into it, to avoid dealing with the owners manually with `addSource`.


#### Distinction Operators

Both streams and signals have various `distinct*` operators to filter updates using `==` or other comparisons. These can be used to make your signals behave like they did prior to v15.0.0 (see [blog post](https://laminar.dev/blog/2023/03/22/laminar-v15.0.0#no-more-automatic--checks-in-signals)), or to achieve different, custom logic:
Expand Down

0 comments on commit adb9788

Please sign in to comment.