Reactive programming is a declarative programming paradigm focused on data streams and the propagation of change. RxJS (Reactive Extensions for JavaScript) is a library that makes reactive programming in JavaScript possible and practical.
In this notebook, we'll explore the fundamental concepts that make reactive programming powerful for handling asynchronous operations, event-based systems, and complex data flows.
$ npm install rxjs added 2 packages in 2s 28 packages are looking for funding run `npm fund` for details
RxJS loaded successfully
Observables are the core building block in RxJS. They represent a stream of values over time that you can observe and react to. Think of an Observable as a sequence of data that can be emitted either synchronously or asynchronously.
Before subscribe Observable execution started Received: First value Received: Second value After subscribe
RxJS provides many ways to create Observables from different sources.
from array example: Array item: 1 Array item: 2 Array item: 3 from promise example: Promise result: Promise resolved
of operator example: Fruit: Apple Fruit: Banana Fruit: Cherry
interval example: Interval: 0 Interval: 1 Interval: 2 Interval: 3 Interval: 4 Interval complete
fromEvent example (simulated): Click at: {"x":100,"y":200} Click at: {"x":150,"y":250} Click at: {"x":200,"y":300} Click stream completed
Marble diagrams are a way to visualize Observable sequences and operator behavior. They represent time flowing from left to right, with marbles representing emitted values.
For example, here's what a marble diagram for a simple Observable might look like:
input: --1--2--3--4--|
map(x => x * 10)
output: --10-20-30-40--|
Let's see this in action:
Marble diagram demonstration: input: --1--2--3--4--| map(x => x * 10) output: --10-20-30-40--| [202ms] Input: 1 [204ms] Output: 10 [452ms] Input: 2 [452ms] Output: 20 [665ms] Input: 3 [667ms] Output: 30 [871ms] Input: 4 [871ms] Input complete [871ms] Output: 40 [872ms] Output complete
Operators are pure functions that transform, filter, or combine observables. They are the primary way to manipulate data streams in reactive programming.
Basic transformation operators: Doubled: 2 Doubled: 4 Doubled: 6 Doubled: 8 Doubled: 10 Doubled: 12 Doubled: 14 Doubled: 16 Doubled: 18 Doubled: 20 Even number: 2 Even number: 4 Even number: 6 Even number: 8 Even number: 10 Squared odd: 1 Squared odd: 9 Squared odd: 25 Squared odd: 49 Squared odd: 81
Complex data transformation example: Transformed users: [ { "id": 1, "displayName": "Alice", "isAdmin": true }, { "id": 3, "displayName": "Charlie", "isAdmin": false }, { "id": 4, "displayName": "Diana", "isAdmin": true } ]
Flattening operators are crucial for working with nested Observables, which are common when dealing with HTTP requests, user interactions, and other asynchronous operations.
Problem with nested observables: Searching for: rx Searching for: rxjs Results for "rx": [Results for rx] Results for "rxjs": [Results for rxjs]
Flattening operators example: 1. mergeMap - parallel processing: Searching for: react API call for: react Searching for: rxjs API call for: rxjs 2. switchMap - cancels previous: Searching for: react API call for: react Searching for: rxjs API call for: rxjs 3. concatMap - sequential processing: Searching for: react API call for: react mergeMap results: [Results for rxjs] switchMap results: [Results for rxjs] mergeMap results: [Results for react] concatMap results: [Results for react] Searching for: rxjs API call for: rxjs concatMap results: [Results for rxjs]
Understanding the difference between hot and cold Observables is fundamental in reactive programming.
Cold Observable example: First subscriber: Cold Observable producer created with value: 44 Subscriber 1 received: 44 Second subscriber: Cold Observable producer created with value: 36 Subscriber 2 received: 36
Hot Observable example: Hot Observable producer generated value: 69 Hot subscriber 1 received: 69 Hot Observable producer generated value: 95 Hot subscriber 1 received: 95 Hot subscriber 2 received: 95
Converting cold to hot with share(): Without sharing (two separate intervals): Source emitted: 0 First subscriber: 0 Source emitted: 1 First subscriber: 1 Source emitted: 0 Second subscriber: 0 Source emitted: 2 First subscriber: 2 Source emitted: 1 Second subscriber: 1 Source emitted: 2 Second subscriber: 2 With sharing (multicasting): Shared source emitted: 0 Shared first: 0 Shared source emitted: 1 Shared first: 1 Shared second: 1 Shared source emitted: 2 Shared first: 2 Shared second: 2
Subjects are special types of Observables that allow multicasting and act as both Observable and Observer.
Basic Subject example: Subscriber A: Hello Subscriber B: Hello Subscriber A: World Subscriber B: World Subscriber A: Still listening? Subscriber B: Still listening? Late subscriber: Still listening?
Specialized Subjects: 1. BehaviorSubject Example: Behavior subscriber 1: Initial state Behavior subscriber 1: Updated state Behavior subscriber 2: Updated state Current value: Updated state 2. ReplaySubject Example: Replay subscriber: Second Replay subscriber: Third 3. AsyncSubject Example: Async subscriber 1: Will see only this Async subscriber 2: Will see only this Async late subscriber: Will see only this
Proper error handling is crucial in reactive programming to ensure your application remains resilient.
Error handling strategies: 1. Without error handling: Value: 1 Value: 2 Value: 3 Error caught in subscriber: Something went wrong! 2. Using catchError to recover: Value: 1 Value: 2 Value: 3 Error caught by operator: Something went wrong! Value: Recovered value Completed successfully with recovery 3. Using retry operator: Attempt 1 Attempt 2 Attempt 3 Final value: Success after retries! Completed with retry
Backpressure occurs when a producer emits values faster than a consumer can process them. RxJS provides operators to handle this.
Backpressure handling techniques: 1. Using throttleTime (first-pass): Original: Event 0 Throttled: Event 0 Original: Event 1 Original: Event 2 Original: Event 3 Throttled: Event 3 Original: Event 4 Original: Event 5 Original: Event 6 Throttled: Event 6 Original: Event 7 Original: Event 8 Original: Event 9 Throttled: Event 9 Original: Event 10 Throttle example stopped 2. Using debounceTime (wait for pause): Typing: a Typing: ap Typing: app Debounced result: app Typing: apple Typing: apple p Typing: apple pi Typing: apple pie Debounced result: apple pie Debounce example complete 3. Using sample (periodic sampling): Fast source: 0 Fast source: 1 Sampled value: 1 Fast source: 2 Fast source: 3 Fast source: 4 Sampled value: 4 Fast source: 5 Fast source: 6 Fast source: 7 Sampled value: 7 Fast source: 8 Fast source: 9 Fast source: 10 Sampled value: 10 Fast source: 11 Fast source: 12 Fast source: 13 Sampled value: 13 Fast source: 14 Sample example complete
The true power of reactive programming comes from composing streams. Let's explore how to build complex data flows by combining simple streams.
Stream composition example: User typed: re User typed: rea Making API call for: rea User typed: reac Making API call for: reac User typed: react Making API call for: react User typed: reacti Making API call for: reacti User typed: reactiv Making API call for: reactiv Results for "reactiv": ["reactiv result 1","reactiv result 2"] Search history size: 0 Search stream completed
Let's look at some common reactive patterns used in real-world applications.
State management pattern: Todo list updated: [] Filter changed to: all Adding todos... Todo list updated: [{"id":1,"text":"Learn RxJS","completed":false},{"id":2,"text":"Build reactive app","completed":false}] Completing todo... Todo list updated: [{"id":1,"text":"Learn RxJS","completed":true},{"id":2,"text":"Build reactive app","completed":false}] Changing filter... Filter changed to: completed
Caching pattern with shareReplay: Making expensive API call... First subscriber subscribes Data fetched: {"data":"Valuable data","timestamp":1741960097244} First subscriber got data (time=1741960098252) Second subscriber subscribes (should use cache) Second subscriber got data (time=1741960098753) Third subscriber subscribes (should use cache) Third subscriber got data (time=1741960099250) Caching example complete
Let's finish by summarizing the core principles of reactive programming:
Reactive programming with RxJS gives you powerful tools to handle complex async operations, UI events, and data management in a consistent, maintainable way. While it has a learning curve, mastering these core concepts will significantly improve how you handle asynchronous programming in JavaScript applications