Netflix Replaces Asynchronous Callbacks With Reactive Streams

Netflix JavaScript Talks - Async JavaScript with Reactive Extensions — Matt Marenghi

Matt Marenghi

Treating user events as collections over time eliminates race conditions and memory leaks by replacing manual listener management with declarative stream cancellation.

AI-assisted summary of Netflix JavaScript Talks - Async JavaScript with Reactive Extensions — original source. Check the original for full context.

Source published:

Key ideas

  • Netflix restructured its client and server engineering around Reactive Extensions (Rx), treating asynchronous events and collections as mathematical duals. (6:01)
  • By adding an explicit completion notification to the classic Gang of Four Observer pattern, event streams support sequential flattening and deterministic race condition handling. (2:51)
  • Declarative lifecycle combinators like `takeUntil` eliminate the need for manual unsubscription and listener tracking, ending memory leaks from forgotten teardowns. (18:44)
  • Netflix open-sourced its server-side port RxJava and released interactive JavaScript exercises to standardize reactive functional patterns across the developer community. (27:55)

Detailed notes

## Background Published on YouTube by Matt Marenghi on March 22, 2014, this inaugural episode of 'Netflix JavaScript Talks' explores how Netflix leverages the Reactive Extensions (Rx) library to construct responsive, event-driven user experiences. According to the page description, the talk covers declaratively modeling complex events out of simple streams, coordinating Ajax requests, reactively updating user interfaces, eliminating memory leaks from forgotten event listeners, and gracefully handling asynchronous exceptions.

## TL;DR Drawing from real-world architectural challenges in Netflix's web UIs and video players, the speaker explains how escalating asynchronous complexity—characterized by state explosion, race conditions, bypassed error handling, and memory leaks—prompted the engineering team to abandon callback hell. By viewing asynchronous event streams as collections over time, engineers can apply familiar functional operators such as map, filter, concatAll, and takeUntil to orchestrate complex interactions like drag-and-drop gestures, throttled autocomplete search, and video authorization pipelines. The talk demonstrates how completing streams enables automatic unsubscription and unified error handling, culminating in the widespread adoption of Reactive Extensions (RxJS and RxJava) across both client and server tiers at Netflix.

## Atmosphere & style The presentation carries the engaging, confident cadence of an experienced tech lead holding court at an engineering meetup. The speaker balances deep computer science theory—such as the historical duality between Gang of Four iterator and observer patterns—with playful audience banter, relatable jokes about slide boilerplate, and real-world anecdotes from Netflix's production trenches. Watching it feels like witnessing a lightbulb moment unfold in real time, as seemingly disparate challenges in UI event coordination and distributed network calls are elegantly unified under a single, declarative abstraction.

## Bulletin - [06:01](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=361s) Netflix restructured its client and server engineering around Reactive Extensions (Rx), treating asynchronous events and collections as mathematical duals. - [02:51](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=171s) By adding an explicit completion notification to the classic Gang of Four Observer pattern, event streams support sequential flattening and deterministic race condition handling. - [18:44](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1124s) Declarative lifecycle combinators like `takeUntil` eliminate the need for manual unsubscription and listener tracking, ending memory leaks from forgotten teardowns. - [27:55](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1675s) Netflix open-sourced its server-side port RxJava and released interactive JavaScript exercises to standardize reactive functional patterns across the developer community.

## Key takeaways - [00:00](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=0s) Compounding asynchronous JavaScript tasks—such as player hardware initialization, network calls, and UI sync—historically led to callback hell, state explosion, and bypassed try/catch error handling. - [02:51](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=171s) The Iterator and Observer patterns from the 1994 Gang of Four book are duals: the Iterator pulls data synchronously until done, while the Observer has a producer pushing items to a consumer. - [02:51](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=171s) The crucial historical omission in the traditional Observer pattern was a standardized completion event to signal when no further data will ever arrive. - [06:01](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=361s) Events can be mathematically conceptualized as collections spread over time, allowing asynchronous streams to be manipulated with standard functional operators like map and filter. - [10:19](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=619s) Complex asynchronous interactions such as DOM drag-and-drop can be represented as declarative queries by mapping mouse-down events to mouse-move streams that complete on mouse-up. - [12:26](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=746s) The Observable interface unifies the three pillars of frontend development—DOM events, network data requests, and UI animations—under a single composable abstraction. - [13:36](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=816s) Observable subscriptions establish three distinct semantic channels: next value emission, error propagation, and completion notification. - [15:30](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=930s) The concatAll operator waits for each inner Observable to complete before subscribing to the next, enforcing deterministic execution order and eliminating race conditions. - [18:44](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1124s) Declarative stream termination using takeUntil allows streams to auto-unsubscribe upon completion, completely avoiding manual listener removal and memory leaks. - [20:41](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1241s) Complex autocomplete interactions resolve race conditions and network instability by combining throttling, retry policies, and takeUntil cancellation on subsequent key presses. - [20:41](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1241s) When a network operation is cancelled via takeUntil, it completes as an empty collection and disappears entirely when flattened, avoiding complex manual rollback state machines. - [25:00](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1500s) Netflix rebuilt its video player state machine around Observables, allowing authorization requests and hardware initialization to be seamlessly sequenced and cancelled upon user exit. - [26:59](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1619s) Observables are used universally across Netflix's architecture, including client startup orchestration, server-to-client network data pipelines, animation coordination, and reactive data binding. - [27:55](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1675s) Netflix created RxJava to port reactive stream paradigms to its backend microservices and published internal interactive JavaScript training materials for the wider community.

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=0s) The Reality of Async Programming at Netflix and Callback Hell

As the cross-team technical lead for Netflix, the speaker works across all of the company's various user interfaces, the vast majority of which are authored in JavaScript. Bringing around 13 years of industry experience—including stints at GE and Microsoft, alongside roughly six years designing systems with Functional Reactive Programming (FRP)—the speaker sets out to tell the story of how Netflix tackled its massive asynchronous challenges by fundamentally rethinking events. Virtually all modern JavaScript applications are intrinsically asynchronous: boot-up workflows require orchestrating platform subsystems while awaiting network responses, video players must cleanly start and abort playback, data fetches run perpetually in the background, UI animations need coordination, and views must synchronize with changing models. Left unchecked, this compounding asynchronous behavior causes application complexity to spiral out of control.

Three years earlier, Netflix found itself trapped in "callback hell," wrestling to coordinate intricate asynchronous control flows. Drawing on an example straight out of an early video player, the speaker demonstrates why async logic is notoriously painful:

- **State explosion**: Waiting for multiple in-flight asynchronous operations to finish requires introducing intermediate tracking variables (such as checking whether a movie authorization ticket has arrived or whether player hardware has initialized), forcing engineers to hand-maintain delicate state machines across disparate callbacks. - **Defeated error handling**: Whereas synchronous code relies heavily on `try/catch` to cleanly propagate exceptions up the stack, asynchronous runtimes render `try/catch` essentially useless, forcing developers to accumulate yet more boilerplate variables just to store error states. - **Resource leaks and races**: Asynchronous, event-driven patterns constantly invite memory leaks when developers inadvertently fail to unsubscribe from listeners (such as an un-deregistered listener on a cancel button). Compounding these leaks, any scenario doing two things at once opens the door to race conditions.

As the speaker summarizes the core architectural problem of that era: “This is really the story of how Netflix solved big async problems by thinking differently about events.”

```mermaid flowchart TD accTitle: Pitfalls of Callback-Driven Asynchrony accDescr: Diagram illustrating the compounding failure modes when coordinating multiple async operations using manual callbacks.

subgraph Operations["In-Flight Operations"] OP1["Auth / Movie Ticket Request"] OP2["Player Hardware Init"] EV["User UI Action (Cancel Button)"] end

subgraph Hazards["Callback Hell Pitfalls"] ST["Manual State Variables\n(flag tracking & synchronization)"] ERR["Bypassed try/catch\n(manual error tracking state)"] LEAK["Dangling Listeners\n(forgotten unsubscriptions -> memory leak)"] RACE["Race Conditions"] end

OP1 --> ST OP2 --> ST OP1 -.-> ERR OP2 -.-> ERR EV --> LEAK ST --> RACE ```

---

[02:51](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=171s) The 20-Year Historical Divide: Iterators vs. Observers

To diagnose why asynchronous architecture arrived at such a broken state, the speaker looks back 20 years to 1994—a year marked by the film *Speed* and Weezer's Blue Album—when the Gang of Four (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) published their seminal book, *Design Patterns: Elements of Reusable Object-Oriented Software*. Joking with the audience about dating themselves if they had read it, or merely having it gather dust on their desks, the speaker directs attention to the comprehensive relationship diagram inside the book to isolate two specific design patterns: the **Iterator** pattern and the **Observer** pattern.

```mermaid flowchart LR accTitle: Duality between Iterator and Observer Patterns accDescr: Contrast between consumer-driven pull in iterators and producer-driven push in observers.

subgraph PullModel["Iterator Pattern (Synchronous / Pull)"] ConsumerP["Consumer"] -->|"requests next item"| IteratorP["Iterator"] IteratorP -->|"yields item until 'done'"| ConsumerP end

subgraph PushModel["Observer Pattern (Asynchronous / Push)"] ProducerO["Producer / Emitter"] -->|"pushes item to callback"| ConsumerO["Consumer / Observer"] ProducerO -.->|"missing: explicit 'complete' signal"| ConsumerO end ```

The speaker contrasts how these two complementary ideas developed apart:

- **The Iterator Pattern (Pull Model)**: Standardized to allow consumers to traverse any arbitrary in-memory collection (lists, sets, trees) without exposing internal structures. In this model, the consumer remains in complete control: it requests an iterator from the producer and pulls the next item iteratively until the iterator announces there is no more data left. - **The Observer Pattern (Push Model)**: The foundational ancestor of modern pub/sub systems and Node.js `EventEmitter`s. Here, the roles reverse: the consumer supplies a callback to a data producer, which pushes data items forward over time. - **The Missing Piece**: Although both patterns model sequences of items delivered progressively, the original Gang of Four Observer pattern omitted an explicit, standardized notification for completion. The producer had no built-in semantic hook to inform the listener that no further data would ever arrive.

Reflecting on how complementary the two systems actually are once completion is added, the speaker observes: “The iterator pattern and the observer pattern aren't really that different. In the iterator pattern the consumer is in control and it's pulling data one item at a time until finally the iterator says I'm done. But in the observer pattern... the producer iterates you.”

---

[06:01](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=361s) Bridging Collections and Events: The Question that Changed Everything

Around 2009, while working at Microsoft, the speaker met computer scientist Erik Meijer, who asked a deceptive question: *"What is the difference between an array and an event?"* That conceptual leap—realizing that an in-memory collection and an asynchronous stream of events over time are mathematically dual representations of the exact same concept—completely rewired how the speaker approached software design. Returning to Netflix, the team spent two and a half years structuring their production applications around the principle that events are simply collections spread over time.

Before demonstrating how to program over event streams using collection operators, the speaker steps aside for a humorous mini-tutorial on ECMAScript 6 (JavaScript 6). Clarifying that functional reactive programming works independently of ES6, the speaker admits two ulterior motives: first, as a member of the TC39 committee, he wants to build community excitement for the standard; second, ES6 arrow functions dramatically cut down slide boilerplate:

- **JavaScript 5 function syntax**: ```javascript function (x) { return x + 1; } ``` - **JavaScript 6 arrow syntax**: ```javascript x => x + 1 ```

Pausing for applause as the code shrinks on screen, the speaker jokes, “The slides just got a lot shorter as you can imagine... You're all JavaScript 6 experts now.”

---

[07:30](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=450s) Standard Collection Operators: forEach, Map, and Filter

The speaker introduces the core functional tools, noting that nearly all of Netflix's asynchronous application logic is built on top of a tiny handful of generic operations. While these primitives initially appear to belong strictly to traditional synchronous arrays, they form the foundation for stream manipulation:

- `forEach`: Takes an array and invokes a callback once for every item in sequence. For example, logging across `[1, 2, 3]` sequentially yields `1`, `2`, and `3`. - `map`: Takes an array, applies a transformation projection function to each element, and outputs an entirely new collection containing the results. When the speaker asks the room what mapping `x => x + 1` over `[1, 2, 3]` produces, the audience choruses the expected transformed values: `[2, 3, 4]`. - `filter`: Evaluates each element against a predicate function to decide which values are retained in the resulting stream.

Emphasizing how foundational these simple building blocks are before translating them to asynchronous contexts, the speaker stresses: “The vast majority of Netflix's async code is written using just these few flexible functions, and at first when I show them to you, they're going to seem to have nothing whatsoever to do with async programming.”

[08:24](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=504s) The Triad of Operations and Querying the Netflix "Lolo"

The review of foundational array combinators continues by defining `filter` and introducing `concatAll`. While `filter` applies a boolean test across every item in a collection to return a new collection containing solely the elements that pass, `concatAll` addresses multidimensionality. It takes a nested collection and flattens it by exactly one dimension (for instance, turning a two-dimensional array into a flat one containing elements 1, 2, 3, 4). The speaker points out that while `concatAll` is not currently in JavaScript nor planned for the upcoming version, it is trivial to implement. Together, `map`, `filter`, and `concatAll` form a toolkit capable of expressive data transformations.

```mermaid flowchart TD accTitle: Flattening nested genre video lists accDescr: Shows the transformation of a user object into flattened top-rated videos using map, filter, and concatAll. subgraph Input["User Video Data"] U["User Object"] --> VL["user.videoLists"] end subgraph Transform["Map & Filter"] VL -->|"map each list"| FL["Filter: rating === 5"] FL --> 2D["2D Collection (filtered lists)"] end subgraph Output["Flatten & Consume"] 2D -->|"concatAll()"| Flat["Flat Top-Rated Films Collection"] Flat -->|"forEach()"| UI["Render / Print Titles"] end ```

To demonstrate their utility, the speaker connects these functions to Netflix's real-world product: the "Darwin" user interface developed over the preceding year, which the speaker calls "absolutely beautiful." The underlying data structure representing the Netflix catalog is often affectionately dubbed a "Lolo"—a list of lists of movies organized by genre, where each genre list contains individual video objects. To extract a user's favorite titles, the speaker writes `getTopRatedFilms(user)`, iterating over `user.videoLists` with `map`. For each list, it filters the videos down to those with a rating of five. Because mapping each list to another filtered list results in a two-dimensional collection, `concatAll` is appended to collapse the structure into a single, flat stream of 5-star movies, which can then be cleanly traversed using `forEach`.

- `concatAll` is not native to ECMAScript 5 or ES6, but serves as a fundamental flattening operation. - In Netflix parlance, a "Lolo" stands for a "list of lists," the architectural backbone of their genre-and-row UI. - Reminding the audience of modern syntax shorthand, the speaker quips, “remember arrow means function people right”.

---

[10:19](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=619s) Reimagining DOM Events: Drag-and-Drop as a Collection

Setting up an unexpected parallel, the speaker challenges the audience: what if the exact same query code used to filter Netflix titles could be repurposed to implement a mouse-drag interaction on a DOM element? When the audience plays along with skepticism, the speaker responds with satisfaction: “No way, thank you that's what I was waiting for.” Switching the visual slide, the speaker illustrates that if DOM events are conceptualized not as imperative callbacks tied to `addEventListener` and `removeEventListener`, but rather as collections over time, event coordination becomes identical to array processing.

```mermaid flowchart LR accTitle: Drag Event Stream Composition accDescr: Shows mouse down events mapped to document mouse moves stopped by mouse up, then flattened. MD["mouseDown Collection"] -->|"map"| MM["document.mouseMove"] MM -->|"takeUntil"| MU["mouseUp Collection"] MU -.->|"signals completion"| MM MM -->|"produces 2D streams"| C["concatAll()"] C -->|"flat point stream"| FE["forEach() -> updates element style"] ```

A mouse-drag gesture is broken down into simple primitives. For every `mouseDown` event on an element, the program tracks `mouseMove` events across the document. In the film example, `filter` reduced the quantity of elements; here, a helper called `takeUntil` curtails the stream. `takeUntil` monitors a source collection alongside a stop collection—in this case, `mouseUp`. The instant the stop collection emits an item, the underlying event stream completes. Because every `mouseDown` yields a nested stream of bounded `mouseMove` coordinates, mapping creates a two-dimensional event collection. Applying `concatAll` flattens these distinct bursts of movement into an unbroken stream of coordinates active only while the mouse button is depressed. Finally, subscribing via `forEach` consumes each incoming point to update the DOM element's position on screen.

- The speaker directly equates adding an event listener with traversing a data structure: “When you subscribe up to an event you are traversing a collection when you add an event listener that's all you're doing traversing a collection.” - `takeUntil` introduces explicit stream completion, providing the missing piece of the standard Gang of Four Observer pattern.

---

[12:26](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=746s) The Observable and Reactive Extensions

Addressing the audience's intrigue, the speaker explains the core abstraction powering this parity: the *Observable*, which the speaker characterizes as a collection type discovered only about five years prior. The speaker defines it crisply: “an observable is just a collection of items over time.” This construct originates from the Reactive Extensions (Rx) library. Rx projects all familiar functional array methods onto the Observable interface, adding streaming utilities to filter, aggregate, and project asynchronous sources.

Beyond its origins in the.NET ecosystem (C#, VB.NET), Rx became open-source, expanded to JavaScript, and was ported by Netflix to Java to run heavily across their backend server tier. Observables hold profound value for frontend applications because they provide a unified abstraction for the three pillars of user interfaces: events, data requests, and animations. Wrapping standard DOM events into Observables is straightforward using helper utilities such as `Observable.fromEvent(domElement, 'mousemove')`.

```mermaid flowchart TD accTitle: Three Pillars of UI Unified by Observables accDescr: Illustrates how events, network requests, and animations are unified under the Observable abstraction. subgraph UI Pillars E["Events (DOM interactions)"] D["Data Requests (Ajax / Network)"] A["Animations (Time / Ticks)"] end E --> Obs["Observable Type (Collection over Time)"] D --> Obs A --> Obs Obs --> Ops["Combinators: map, filter, concatAll, merge, zip"] ```

- Reactive Extensions has implementations across multiple languages including C#, VB.NET, JavaScript, and Java. - Netflix relies heavily on its own Java port of Rx on the server side to coordinate microservice calls. - In user interface development, events, remote requests, and animation loops can all be represented as identical stream primitives.

---

[13:36](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=816s) Subscriptions, Disposal, and Unified Error Handling

The speaker contrasts the traditional approach to events against the Observable model. Historically, developers attach listeners via `addEventListener` and manually detach them via `removeEventListener`. Observables match this lifecycle directly: calling `forEach` (or `subscribe`) returns a disposable subscription object. When a consumer no longer wishes to receive values from the producer, calling `dispose()` tears down the pipeline. However, transforming events into first-class objects provides functional powers impossible with bare listeners, enabling operators like `map`, `filter`, `reduce`, `merge`, and `zip`.

Crucially, the Observable version of `forEach` handles the two traditional weak points of asynchronous systems: exception propagation and stream completion. Synchronous code relies on `try/catch` to consolidate error handling at call boundaries; Observables mirror this convenience by piping failures downstream into an explicit error callback passed to `forEach`. Developers are freed from checking for errors at every intermediate step of a `map` or `filter` chain. Additionally, an on-completed callback alerts listeners the moment a sequence finishes, unlocking new patterns of coordination that typical open-ended event handlers cannot support.

> “I don't have to check for errors at every single time I do a map or a filter I can wait until I forEach over that observable and check for errors right at the very top.”

- Subscriptions return a token that can be disposed of directly, avoiding detached listeners and memory leaks. - The Observable lifecycle features three separate event channels: next value emission, error propagation, and complete notification.

---

[15:30](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=930s) Race Conditions and Stream Flattening via ConcatAll

To solidify mental models, the speaker invents an imaginary, literal-heavy JavaScript syntax to visualize streaming collections. Applying `forEach`, `map`, or `filter` to an Observable looks conceptually identical to performing those same operations on an in-memory array, with the single difference that emissions arrive spaced out over time: “All the skills that we learned over arrays can now be applied to events.”

```mermaid flowchart LR accTitle: ConcatAll Sequential Stream Processing accDescr: Displays how concatAll handles an Observable of Observables by sequencing inner streams upon completion. subgraph Outer["Observable of Streams"] S1["Stream 1: emits '1' -> Completes"] S2["Stream 2: emits '2' -> Completes"] S3["Stream 3: emits '3'..."] end Outer -->|"concatAll()"| Out["Flattened Output: 1, 2, 3"] ```

The speaker moves to one of the hardest problems in asynchronous systems: race conditions. In synchronous programming, instructions execute sequentially, but listening to multiple concurrent asynchronous sources invites non-deterministic interleaving. Because standard DOM events lack a built-in completion signal, writing a true concatenation operator for them was historically impossible. With Observables signaling completion, `concatAll` can safely process an Observable of Observables. When the parent sequence produces an inner stream, `concatAll` listens, pipes out its values, waits for it to complete, and only then subscribes to the subsequent stream. This guarantees strict sequential ordering across asynchronous operations without manual callback nesting or manual state machines.

- Race conditions emerge when multiple concurrent events fire unpredictably, breaking chronological assumptions. - By relying on the `complete` notification, `concatAll` enforces deterministic execution order across multiple asynchronous streams.

[17:21](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1041s) Sequencing Streams with `concatAll` and `takeUntil`

When working with higher-order event streams, timing and order of execution are paramount. Rather than listening to an incoming inner Observable immediately, `concatAll` waits until the current inner Observable completely finishes before subscribing to the next one. If subsequent Observables arrive while the current one is still running, they are simply buffered in a queue. Once an active stream completes, the next queued stream is pushed through. If an inner stream turns out to be empty, flattening causes it to simply vanish from the resulting sequence without emitting data. By introducing this single concept—allowing an event stream to announce that it is finished—operations like concatenation become possible over asynchronous events.

This same completion mechanic enables `takeUntil`. The operator listens to a source data stream and forwards its emissions until a second "stop" collection emits an event. The moment that stop trigger fires, `takeUntil` completes the overall stream. In functional programming with collections, operators like `map`, `filter`, and `takeUntil` build brand-new collections out of existing ones. In the case of `takeUntil`, it bridges two separate event sources, terminating the downstream pipeline without requiring imperative teardown logic.

```mermaid flowchart LR accTitle: "takeUntil Stream Termination Flow" accDescr: "Data stream emits values downstream until a stop stream emits an item, completing and terminating the pipeline."

subgraph Inputs["Event Sources"] Source["Source Stream<br/>(e.g., mouse moves)"] Stop["Stop Stream<br/>(e.g., mouse up)"] end

subgraph Operator["takeUntil"] Bridge{"Has Stop<br/>stream fired?"} end

subgraph Output["Result Stream"] OutData["Emit Event"] Done["Complete Stream"] end

Source --> Bridge Stop -->|"Fires event"| Bridge Bridge -->|"No"| OutData Bridge -->|"Yes"| Done ```

---

[18:44](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1124s) Declarative Teardown: Why Nobody Needs to Unsubscribe

The ability for an event stream to say "I'm done" solves one of the oldest, most pervasive issues in JavaScript development: forgotten unsubscriptions and memory leaks. Standard DOM events like `document.onload` inevitably complete, yet they keep hold of event listeners indefinitely. As Jafar Husain jokes, listening to such events is like lending your car keys to a friendly neighbor so he can fetch beer; he brings back the beer, but then stands there awkwardly holding your keys until you explicitly ask for them back. Because traditional event models never signal completion, handlers linger in memory unless manually torn down.

Observables invert this model by allowing streams to auto-unsubscribe as soon as they complete. Jafar shares that embracing this philosophy completely transformed his day-to-day coding: “I haven't unsubscribed from an event in the last 5 years.” Instead of juggling listener handles across callbacks, developers can declaratively compose lifetime boundaries directly into the stream definition.

> “That's why we think about events very, very differently here at Netflix: we don't unsubscribe, we cancel events out, we declaratively describe the conditions under which that event stream is supposed to end, and then it cleans up for us.” — Jafar Husain

- **Elimination of bookkeeping:** Developers no longer store listener IDs or reference instances solely to remove them later. - **Declarative cancellation:** Two complementary streams (such as a drag source and a mouse-up terminator) cancel each other out naturally. - **Automated memory management:** Disposing of or completing a stream automatically unsubscribes all underlying source collections.

---

[20:12](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1212s) Re-Evaluating the Mouse Drag Stream

With a concrete understanding of stream flattening and completion semantics, the earlier mouse drag implementation becomes simple and transparent. Every time a `mousedown` event occurs, the pipeline generates a fresh `mousemove` collection scoped to the drag operation. Because this inner stream is decorated with `takeUntil(mouseups)`, it terminates the instant the user releases the mouse button.

Crucially, terminating the inner stream unsubscribes the application from both the `mousemove` and `mouseup` listeners on the spot. At the same time, the root `mousedown` stream remains active, waiting to kick off the next potential drag interaction. The developer does not write manual teardown logic or maintain transient state variables; the lifecycle of the drag operation is fully expressed through the composition of simple event streams.

---

[20:41](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1241s) Building Autocomplete Search Without Race Conditions

Building an autocomplete search box is notorious for exposing asynchronous edge cases. While it sounds simple enough to build before 5:00 PM, engineers often stay stuck until 9:00 PM dealing with subtle bugs. The first problem is request volume: if a user rapidly types "ABCDEF", the client should not bombard the server with six separate HTTP calls. The input must be throttled or debounced so requests only fire once typing pauses. The second, far more treacherous problem is race conditions: if a user types "A" and then quickly types "B", the in-flight network request for "A" might return *after* the request for "AB", overwriting fresh results with stale data.

RxJS provides a clean, declarative solution by modeling the text box as a stream of key presses. Applying `throttle` reduces rapid keystrokes to isolated inputs. Each query is then projected via `getJSON` into an inner Observable that emits a single array of results. Intermittent network issues are handled by attaching `.retry()`, which internally counts failures inside its `forEach` handler and transparently resubscribes to the request before finally bubbling an error if attempts are exhausted.

```mermaid flowchart TD accTitle: "RxJS Autocomplete Pipeline" accDescr: "Pipeline from key presses through throttling, HTTP request with retry, takeUntil cancellation, and flattening to UI update."

subgraph UserInput["User Input Pipeline"] KP["Key Presses Stream"] TH["throttle()"] KP --> TH end

subgraph RequestPipeline["Request Handling"] GJ["getJSON(query)"] RT["retry(count)"] TU["takeUntil(nextKeyPress)"]

TH -->|"Debounced query"| GJ GJ --> RT RT --> TU end

subgraph Resolution["Output Resolution"] FL["Flatten / concatAll"] UI["forEach: Update UI / Handle Error"]

TU --> FL FL --> UI end ```

To eliminate race conditions, each in-flight HTTP Observable is chained with `takeUntil` pointing to the next keypress. If the user types "B" while the query for "A" is still in flight, the "A" Observable immediately completes before it can emit. When the outer stream flattens, that aborted request is simply an empty collection, and flattening an empty collection causes it to vanish completely. As Jafar emphasizes:

> “I don't think about this as a state machine of rolling back states or canceling requests; I just complete a collection, and it's empty, and I flatten, and then it just goes away, and now the only thing outgoing is my second request for AB.” — Jafar Husain

- **Functional composition for events:** Just as simple functions compose into complex business logic, basic streams (`keypress`, `getJSON`) compose into high-level event sequences. - **Resilience out of the box:** Network fragility is isolated and recovered via declarative operators like `retry`. - **Clean UI termination:** The final subscription runs a simple `forEach` that updates the DOM with new search results or displays an error message if the server is down.

---

[25:00](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1500s) Tackling the Netflix Video Player Workflow

Beyond search, Netflix extended Reactive Extensions across its client application, specifically targeting the complex state machine of the video player. Earlier architectures suffered from severe callback hell because launching a video requires coordinating numerous asynchronous subsystems: platform initialization, user authorization, DRM, encryption checks, and license verification.

Instead of chaining nested callbacks or imperatively checking readiness flags, the player architecture treats playback as an Observable collection. The initialization of the underlying hardware platform is encapsulated as an initialization stream that emits exactly once when the player is ready. By mapping over this initialization stream and listening for movie playback attempts, the system coordinates authorization and playback as an organized pipeline of dependent streams.

[26:01](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1561s) Handling Movie Authorization and Cancellation

The speaker walks through the architecture of authorizing playback for a movie using Observable streams. When a movie is selected, the application calls an authorization method with the movie ID, which yields an Observable representing the license. Like a single search result, this stream represents an asynchronous operation that waits for a period before emitting the license. However, the system must gracefully handle scenarios where the user cancels playback—such as by pressing the back button. Instead of having to imperatively manage network request cancellations or defend against delayed responses arriving at the wrong time, the stream can simply be completed early so that it becomes empty.

```mermaid flowchart LR accTitle: Playback Authorization Flow accDescr: Diagram showing how movie authorization handles cancellations and feeds into player playback.

subgraph Auth ["Authorization Stream"] A["Movie ID"] --> B["authorize(id)"] B --> C["License Stream"] end

subgraph Handling ["Flatten & Handle"] C --> D{"User Canceled?"} D -->|"Yes"| E["Complete Stream Early"] D -->|"No"| F["Emit License"] end

subgraph Playback ["Player Output"] F --> G["Flatten Outer Stream"] G --> H["player.play(license)"] end ```

By flattening this two-dimensional collection of streams, the system produces a clean, uninterrupted stream of authorizations. This inner stream is then flattened into the outer collection tied to application initialization, resulting in a single stream of play authorizations. The application consumes this stream directly: the player is instructed to play the emitted license, and if any error occurs along the path, an error handler reports that playback cannot proceed.

- Calling `authorize` on a movie ID yields an Observable containing a single license after an asynchronous delay. - Cancellation events (such as hitting the back button) complete the license stream so it becomes empty, preventing out-of-order responses or manual teardown logic. - Flattening the nested collections leaves a single stream feeding directly into the playback engine. - As the speaker describes the cleanup guarantees: “Well I just take that dot dot dot license and I complete it so that it's empty and I never need to worry about canceling that outgoing request or about it coming down at the wrong time.”

---

[26:59](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1619s) Universal Observable Adoption Across Netflix

Observables have become pervasive across Netflix's engineering stack, serving as the foundational abstraction for any asynchronous programming task. They coordinate application startup tasks, wait for the underlying hardware platform to initialize, retrieve platform metadata, and power the video player. This architecture bridges client and server development seamlessly: engineers write Rx code on the client and move to the server using the identical concurrency model. Server-side systems assemble large Observables, iterate over them, and push the results over the network stream, where client-side code picks them up directly into an Observable to continue mapping and filtering without missing a step.

Beyond raw network orchestration, Observables drive Netflix's presentation layer. Rich, complex transitions between screens are choreographed via Observables, ensuring that asynchronous animations stay synchronized. Similarly, view and model data binding is powered by Observables, allowing UI views to reactively update whenever the underlying data model changes.

- Observables orchestrate client-side startup dependencies, platform readiness, and metadata loading. - Server-side pipelines build unified Observables that push data down the wire, allowing the client to resume filtering and mapping seamlessly. - Screen transitions, animations, and data-binding between models and views are all coordinated using Observable streams. - The speaker highlights this ubiquitous usage: “So today we use observables everywhere we use observables for really any part of our app that has any kind of async code in it for coordinating all the things that are going on in app startup for platform coming up for metadata coming down.”

---

[27:55](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1675s) Resources, RxJava, and Developer Education

To help developers adopt these reactive patterns, the speaker highlights essential resources and open-source projects. For JavaScript developers, the primary Reactive Extensions (Rx) library offers the core tools needed to implement reactive streams. For the Java ecosystem, Netflix created and open-sourced RxJava, which not only ports Rx patterns to Java but actively pushes the limits of reactive stream capabilities.

To onboard its engineering staff, Netflix created an internal training program equipped with interactive JavaScript exercises hosted on GitHub. These same learning exercises, which have been used to train numerous Netflix engineers to work effectively with Rx, are made publicly accessible to the broader community. The presentation concludes by opening the floor to audience questions.

- **Reactive Extensions Library**: The fundamental resource for JavaScript developers looking to adopt Rx patterns. - **RxJava**: Netflix's open-source Java implementation, which broadens reactive patterns and pushes the boundaries of the Rx paradigm. - **Interactive Exercises on GitHub**: The identical training materials and coding exercises used internally to train Netflix engineers on reactive programming. - Expressing enthusiasm for the ecosystem, the speaker advises: “So if you a JavaScript programmer check this out it's absolutely amazing stuff.”

## Notable quotes > [02:51](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=171s) In the iterator pattern the consumer is in control and it's pulling data one item at a time until finally the iterator says I'm done. But in the observer pattern... the producer iterates you. — Jafar Husain

> [18:44](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1124s) we don't unsubscribe, we cancel events out, we declaratively describe the conditions under which that event stream is supposed to end, and then it cleans up for us. — Jafar Husain

> [15:30](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=930s) All the skills that we learned over arrays can now be applied to events. — Jafar Husain

## Mentioned Netflix, Jafar Husain, Erik Meijer, Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, Gang of Four, General Electric, Microsoft, Design Patterns: Elements of Reusable Object-Oriented Software, ECMAScript 6, TC39, Reactive Extensions (Rx), RxJava, RxJS, Node.js, GitHub

## Video description Netflix JavaScript Talks - Episode 1 Async JavaScript with Reactive Extensions

In this talk, you'll learn how Netflix uses the Reactive Extensions (Rx) library to build responsive user experiences that strive to be event-driven, scalable and resilient. Learn the power of the Observable and how we've simplified our lives through the ability to filter, aggregate, compose and project data streams.

Join us while we discuss how Netflix uses Rx to: - Declaratively build complex events out of simple events (ex. drag and drop) - Coordinate and sequence multiple Ajax requests - Reactively update the UI in response to data changes - Eliminate memory leaks caused by neglecting to unsubscribe from events - Gracefully propagate and handle asynchronous exceptions

[Source](https://www.youtube.com/watch?v=XRYN2xt11Ek)

中文简报

Netflix以响应式流取代异步回调 将用户事件视为随时间推移的集合,通过以声明式流取消替代手动监听器管理,消除了竞态条件与内存泄漏。 演讲者结合Netflix网页端UI和视频播放器中的实际架构挑战,阐述了异步复杂性的不断攀升(表现为状态激增、竞态条件、错误处理被忽视以及内存泄漏)如何促使工程团队彻底告别“回调地狱”。通过将异步事件流视为随时间变化的集合,工程师能够运用map、filter、concatAll和takeUntil等熟悉的函数式操作符,来编排拖放手势、限流自动补全搜索和视频授权流水线等复杂交互。演讲展示了流的完成如何实现自动退订和统一的错误处理,并最终促成Reactive Extensions(RxJS和RxJava)在Netflix客户端与服务端层面的广泛应用。 Netflix围绕Reactive Extensions(Rx)重构了其客户端与服务端工程,将异步事件与集合视为数学上的对偶。 (6:01) 通过为经典的GoF观察者模式增加显式的完成通知,事件流得以支持顺序扁平化和确定性的竞态条件处理。 (2:51) 像`takeUntil`这样的声明式生命周期组合子消除了手动退订和追踪监听器的需要,终结了因遗忘清理操作而导致的内存泄漏。 (18:44) Netflix开源了其服务端移植版本RxJava,并发布了交互式JavaScript练习,以期在开发者社区内推广标准化的响应式函数式模式。 (27:55)

Reading summary

Netflix addressed compounding asynchronous complexity and callback hell across its user interfaces by treating asynchronous event streams as collections over time. Technical lead Jafar Husain argues that unifying iterators and observers with explicit completion semantics allows developers to coordinate complex async workflows, race conditions, and lifecycles using functional collection operators. Unifying Iterators, Observers, and Event Streams (2:51) Asynchronous JavaScript traditionally trapped engineering teams in callback hell characterized by state machine explosion, bypassed try/catch error handling, race conditions, and memory leaks from forgotten listener detachments. Husain traces this architectural divide back to the Gang of Four's 1994 design patterns, contrasting the consumer-driven pull model of the Iterator pattern with the producer-driven push model of the Observer pattern. While both represent progressive sequences, the classic Observer pattern omitted a standardized completion notification to signal when no further data would arrive. Influenced by computer scientist Erik Meijer's observation that arrays and events are mathematical duals, Netflix reframed event streams as collections spread across time. This parity allows developers to reuse core functional collection operators—primarily map, filter, and the multidimensional flattening operator concatAll—to query and transform asynchronous streams identically to in-memory catalog data like Netflix's genre lists. Declarative Teardown and Deterministic Concurrency (17:21) Traditional DOM architectures create memory leaks because listeners linger indefinitely after operations conclude. Reactive Extensions (Rx) mitigates this by providing an Observable abstraction featuring three explicit communication channels: value emissions, error propagation, and completion notifications. Pushing errors directly through the stream mirrors try/catch mechanics at the call boundary, freeing developers from asserting error checks at every intermediate functional transformation. Introducing explicit completion fundamentally transforms lifecycle management through declarative operators like takeUntil. Because a stream completes and automatically tears down its underlying listeners once a terminating event emits, Husain notes that developers no longer need to manage manual unsubscription handles. Additionally, operators like concatAll enforce deterministic execution by waiting for an active inner Observable to complete before subscribing to the next queued stream, preventing asynchronous interleaving without hand-written state variables. Mitigating Asynchronous Hazards in Search and Gestures (20:41) The practical power of stream composition is demonstrated in user interaction patterns like DOM drag-and-drop and autocomplete search. A mouse-drag pipeline bounds document movement by applying takeUntil against mouse-up events, flattening the resulting nested collections so that movement subscriptions automatically terminate the moment the button is released, without leaving dangling listeners. In autocomplete search, fast typing often introduces race conditions where an earlier network request returns after a later one, overwriting fresh results with stale data. RxJS addresses this by throttling keystrokes, attaching retry logic for transient HTTP failures, and terminating in-flight requests using takeUntil when the next keypress occurs. The cancelled request completes immediately into an empty collection, which vanishes upon flattening so that only the latest response updates the user interface. Full-Stack Adoption in Video Playback and Architecture (25:00) Beyond isolated UI widgets, Netflix expanded Observables into an architectural standard spanning the entire application lifecycle. Coordinating video playback previously demanded brittle state coordination across hardware initialization, digital rights management, user authorization, and license acquisition. Encapsulating platform readiness as an Observable pipeline allows authorization streams to chain sequentially; if a viewer aborts playback via the back button, the license stream is completed early to discard pending requests cleanly. This concurrency model operates uniformly across Netflix's stack. Client-side applications use Observables to coordinate startup tasks, orchestrate animated screen transitions, and synchronize data bindings between views and models. On the server side, Netflix developed and open-sourced RxJava to assemble microservice responses into unified streams that push data directly to client-side Observables over the network. Husain concludes that modeling events as collections over time eliminates the manual state management and resource leakage inherent in imperative asynchronous code. To foster adoption across the wider developer community, Netflix open-sourced RxJava and made its internal interactive reactive programming curriculum publicly available.

精炼阅读摘要

Netflix技术主管Jafar Husain指出,通过将异步事件重新构想为“随时间推移的集合”(即Observable),开发团队能够将同步集合操作符与显式流终止机制结合,从而从根本上消除回调地狱、竞态条件与内存泄漏问题。 回调地狱的根源与传统模式缺陷 (0:00) 现代JavaScript应用本质上是高度异步的,涉及底层平台引导、网络请求、播放器状态控制、UI动画及视图与模型同步。Netflix在早期架构中深陷回调地狱,面临三项核心挑战:为协调多个进行中的异步操作(如电影授权与播放器硬件就绪),工程师被迫手动维护包含大量中间标志的状态机;异步运行时使得传统的try/catch异常冒泡失效,需要编写大量模板代码来追踪错误;遗漏的事件监听注销容易导致内存泄漏,且并发执行不可避免地引入竞态条件。 迭代器与观察者:对偶性与完成信号 (2:51) 追溯到1994年GoF《设计模式》,迭代器模式和观察者模式构成了数据消费的两种经典模型。迭代器属于拉取模型,由消费者控制并逐项拉取数据,直至迭代器声明终止;观察者模式则属于推送模型,生产者随时间将数据推入回调。然而传统的观察者模式缺少标准化的“完成(complete)”信号,生产者无法通知消费者数据已终结。通过引入显式的完成通知,事件流得以与内存集合实现数学意义上的对偶,使原本属于数组的转换逻辑完全适用于异步事件。 集合操作符与声明式资源生命周期 在Reactive Extensions(Rx)的Observable模型中,常规的集合操作符如map、filter与用于扁平化的concatAll被无缝映射到事件流上。例如,鼠标拖拽手势可以表达为由mousedown映射生成的mousemove流,并利用takeUntil操作符在监听到mouseup时终止。因为Observable具备next、error和complete三个独立通道,当流完成或被显式处置时,底层事件监听会自动取消。Jafar Husain强调,通过声明式地定义流的终止条件,团队完全摒弃了手动注销监听器的操作,解决了悬空监听与内存泄漏隐患。 消除竞态条件与复杂业务流程编排 (20:41) 流的完成语义使得跨异步序列的安全拼接成为可能。在自动补全搜索框中,输入事件被限制频率后映射为网络请求,并结合takeUntil(在下一次按键发生时触发)与retry操作符,使得较旧且仍在执行中的请求流在下一次按键时自动终止并变为空集合,经展平后彻底消失,消除了返回结果时序颠倒造成的竞态问题。在Netflix的播放器管道中,硬件初始化流与包含取消逻辑的电影授权流同样被建模为高阶Observable,用户中途退出只会导致流提前完成,而不会引发状态残留或无效回包。 端到端普及与工程生态建设 (26:59) Observable在Netflix已演变为跨越客户端与服务端的通用异步抽象。它不仅管理客户端的启动依赖、复杂动画过渡以及视图与模型的数据绑定,还延伸至基于Java构建的服务端微服务体系,后者利用Netflix开源的RxJava在服务端组装并推送Observable流。为了支持这一范式演进,Netflix不仅维护开源实现,还将内部培训工程师使用的交互式JavaScript练习公开于GitHub,以推动全栈反应式编程的应用落地。 该架构演进证明,通过赋予推送模型显式的终止与错误通道,异步事件管理可以完全转化为声明式的函数式集合操作。不过,该体系的有效性高度依赖团队转变底层思维,并需要全套操作符生态的配合支持。

Detailed notes — zh

## Background 本视频由 Matt Marenghi 于 2014 年 3 月 22 日发布在 YouTube 频道,属于“Netflix JavaScript Talks”系列第一期。视频简介指出,本场技术分享主要探讨 Netflix 如何利用 Reactive Extensions(Rx)构建响应式、事件驱动、高扩展且具备韧性的用户体验,深入解析 Observable 的能力,并通过过滤、聚合、组合与投影数据流来简化异步交互、解决内存泄漏以及优雅处理异步异常。 ## TL;DR 演讲者结合在 Netflix 的实战经验,深度剖析了传统 JavaScript 异步编程中“回调地狱”、状态变量爆炸、竞态条件以及无法有效 try-catch 等痛点。他追溯了 1994 年设计模式中的迭代器(Pull)与观察者(Push)的对偶关系,并提出了核心认知飞跃:事件在本质上就是随着时间推移展开的集合(数组)。通过在全新集合类型 Observable 上运用 map、filter、concatAll 和 takeUntil 等操作符,Netflix 将 UI 事件、数据请求与动画完全统一为同构数据流,不仅用极其简练的代码实现了拖拽和防抖自动补全,更消除了手动取消订阅导致的内存泄漏。最终,这种基于 Rx 的响应式模型被贯穿于播放器核心链路以及前后端全栈架构之中。 ## Atmosphere & style 整场演讲呈现出一种典型的技术大厂工程师内敛而极度自信的分享氛围,既有对业界顽疾的尖锐批判,又充满了顿悟时刻的智力快感。主讲人以轻松幽默、富有亲和力的叙事风格展开讲解,不时穿插对《设计模式》落灰的调侃和现场互动,化解了底层数学与架构概念的枯燥感。随着从处理纯内存数组一步步推导至仅用几行流式代码搞定复杂的拖拽与播放器竞态,现场观众的反应从最初的不可置信转为豁然开朗,带给人一种打破传统编程思维定式、见证优雅工程之美的充实感。 ## Bulletin - [00:00](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=0s) Netflix 通过引入 Reactive Extensions (Rx) 将异步编程简化为对“随时间变化的集合”进行操作,有效解决了传统回调地狱带来的状态爆炸问题。 - [00:12](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=12s) Rx 的核心优势在于将事件、网络请求和动画统一为 Observable 抽象,使得开发者能利用 map、filter 等数组算子实现复杂的异步流程组合。 - [00:17](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=17s) Netflix 内部推广了“声明式编程”模式,通过 takeUntil 等机制实现事件流的自动终止,彻底杜绝了因忘记注销监听而导致的内存泄漏。 - [00:27](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=27s) Rx 实现了跨全栈的统一异步模型,Netflix 不仅在前端使用 RxJS,更在后端全面采用 RxJava,实现前后端逻辑思维的高度一致。 ## Key takeaways - [00:00](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=0s) 应用中的异步流程若无统一管控,极易导致状态管理复杂化并引发难以排查的竞态条件。 - [00:01](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1s) 传统回调地狱的根本缺陷在于缺乏错误传播机制以及无法显式通知“数据流已结束”的生命周期管理。 - [00:02](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=2s) 迭代器(拉模型)与观察者(推模型)在本质上是互为对偶的,统一二者的关键在于为观察者模式补齐“数据流完成”的通知能力。 - [00:06](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=6s) 数组与事件的本质差异仅在于时间维度,Rx 成功地将内存集合的函数式操作扩展到了时间序列上。 - [00:08](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=8s) concatAll 操作符是处理嵌套集合(如 Netflix 的 LoLo 列表模型)的关键,能够将二维异步流展平为一维。 - [00:10](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=10s) 鼠标拖拽等复杂 UI 交互可以转化为一组由 mouseup 截断的 mousemove 数据流,完美印证了“事件即集合”的响应式编程理念。 - [00:13](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=13s) Observable 将事件提升为语言的一等公民,使得异步代码能够像同步代码一样通过 try-catch 方式进行集中式的异常捕获。 - [00:17](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=17s) 在 Rx 中,订阅(Subscribe)等同于遍历集合,而取消订阅则通过声明式定义完成条件(Completion)来自动触发。 - [00:20](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=20s) 针对搜索自动补全场景,利用 throttle 减少请求并配合 takeUntil 截断过期请求,是解决竞态条件覆盖问题的标准范式。 - [00:25](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=25s) 视频播放器鉴权重构证明,通过将播放尝试定义为 Observable,无需手动回滚网络请求或状态即可优雅处理用户操作取消。 - [00:26](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=26s) Netflix 的技术栈通过 Rx 实现了从服务端到客户端的无缝对接,允许数据流在不同层级间通过统一的算子管道进行处理。 - [00:27](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=27s) Netflix 已将内部培养开发者掌握 Rx 的交互式练习材料开源,供外部开发者学习函数响应式编程思维。 ## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=0s) 演讲者背景与 Netflix 的异步挑战 演讲者开篇介绍,今天的议题是 Netflix 在处理异步 JavaScript 方面的实战经验。作为 Netflix 的跨团队技术主管(Cross-team Technical Lead),他的工作横跨 Netflix 的各个 UI 产品线,而正如主持人 Matt 所提及的,其中绝大部分 UI 都是用 JavaScript 编写的。演讲者拥有约 13 年的行业从业经历,此前曾先后就职于通用电气(GE)和微软(Microsoft),并在构建函数响应式编程(Functional Reactive Programming, FRP)系统方面积累了约 6 年的经验。他指出,这场演讲的核心主线正是 Netflix 的工程师们如何“通过用截然不同的方式思考事件(events),从而攻克庞大的异步编程难题”。 Netflix 的前端应用以及业界绝大多数 JavaScript 应用本质上都是重度异步的。在应用启动阶段,系统必须协调等待各个平台组件就绪,并等待网络请求返回;在播放器控制中,用户随时可能发起播放或取消操作;后台始终进行着数据拉取与访问;此外还有各种动画的编排,以及将视图绑定到数据模型等等。如果无法妥善管控这些异步过程,应用的复杂度就会彻底失控并发生爆炸。 ```mermaid flowchart TD accTitle: Netflix 常见异步交互场景 accDescr: 展示 Netflix 客户端在启动、播放、网络数据及动画等方面需要协同的大量异步流程。 subgraph "客户端核心异步任务" A["应用启动协调 (Platform Startup)"] B["播放器状态控制 (Play / Cancel)"] C["持续数据访问 (Data Access / Ajax)"] D["动画编排与视图数据绑定 (Animations & UI Bindings)"] end A --> E["极度复杂的异步交织"] B --> E C --> E D --> E E --> F["状态爆炸与难以维护的混乱"] ``` ### [01:03](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=63s) 回顾三年前:回调地狱与异步状态泥潭 演讲者将时间拨回到大约三年前,当时 Netflix 与许多团队一样,正深陷“回调地狱(Callback Hell)”的挣扎之中。所谓回调地狱,本质上就是试图通过嵌套回调去协调一系列复杂操作所带来的混乱局面。演讲者展示了一段源自当年播放器真实逻辑的简化示例代码,并剖析了为什么异步编程会如此艰难: 1. **状态(State)泛滥**:当有两个同时处于在途(in-flight)状态的异步回调,而你必须等待两者全部执行完毕时,唯一的办法就是手动引入变量来跟踪这两个回调的状态。随着需求增加,代码中不得不充斥着各种标志位变量——用来记录电影凭证(movie ticket)是否返回、播放器是否完成初始化等等。这种状态的堆叠导致应用极难扩展,所有状态都必须由开发者手动维护并确保处于正确的状态。 2. **异常处理(Exception Handling)失灵**:在传统的同步编程中,`try/catch` 机制非常健壮,能自动向上冒泡错误并在顶层捕获;然而一进入异步世界,`try/catch` 几乎完全失效,运行环境在捕获和处理异步异常上无法提供任何开箱即用的帮助。为了追踪异步流程中发生的错误,代码里又不得不增加更多专门记录错误状态的变量。 3. **事件内存泄漏(Memory Leaks)与竞态(Race Conditions)**:在事件驱动编程中,只要订阅了事件,就始终伴随着泄漏的风险。演讲者指出代码里特意留的一个 Bug:给取消按钮(cancel button)注册了事件监听,后续却完全忘记注销监听。此外,一旦系统能够同时并发执行两个任务,就必然面临竞态条件的隐患。 演讲者对这套旧有模式的弊端做出了尖锐的概括:“All of a sudden it can get really difficult to scale an app that way because you add all this state and that state's got to be maintained and kept in the right well state.” ### [02:51](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=171s) 溯源 1994 年:迭代器与观察者模式的双生关系 为了探究异步编程究竟是如何演变到如此艰难的境地,演讲者幽默地带领听众把目光投向 20 年前——1994 年。那一年电影《生死时速》(*Speed*)刚刚上映,Weezer 乐队推出了广受好评的《蓝色彩盘》(*Blue Album*),而软件工程史上极具影响力的《设计模式》(*Design Patterns*)也正式出版。现场针对有谁读过这本 GoF(四人帮)名作、谁又只是买来搁在办公桌上积灰进行了互动打趣。 在 GoF 的书中有一张展示模式间相互关系的拓扑图,而在本次演讲中,焦点聚集在其中两个关键模式:**迭代器模式(Iterator Pattern)**与**观察者模式(Observer Pattern)**。 * **迭代器模式(拉模型 / Pull)**:允许消费者以统一的方式从任意数据结构(无论列表还是集合)中拉取数据。消费者的工作模式是向数据生产者索取一个迭代器,不断调用获取下一个元素,直到迭代器指示数据已穷尽。在当时即将到来的 ECMAScript 6(JavaScript 6)规范中,迭代器也即将作为语言原生特性引入。 * **观察者模式(推模型 / Push)**:正是现代 Node.js 等环境中 `EventEmitter` 或发布-订阅(Pub/Sub)机制的前身。在此模式中,由数据生产者按顺序逐个将数据推送给消费者。 演讲者强调,这两种模式本质上处理的是同一件事:消费者与生产者之间逐项传递信息的数据流动。唯一的对称差异在于控制权: - 在迭代器模式中,**消费者处于主控地位**,主动去拉取数据。 - 在观察者模式中,**生产者处于主控地位**,正如演讲者总结道:“In the Observer pattern, the producer iterates you.” 然而,当年的观察者模式留下了一个致命的设计疏漏:在发布-订阅或事件监听体系中,**生产者没有一个标准、良定义的方式去通知消费者“所有数据已发送完毕,此后再无新数据”**。如果为事件体系补齐这种类似迭代器“已结束”的能力,那么针对内存中数组/集合的同步编程范式,就可以无缝迁移并完全套用在基于事件的异步编程上。 ```mermaid flowchart LR accTitle: 迭代器模式与观察者模式的对称关系 accDescr: 比较迭代器拉取数据与观察者推送数据的对称对偶关系。 subgraph "迭代器模式 (Pull - 消费者主导)" Consumer1["消费者 (Consumer)"] -->|"主动请求 next()"| Producer1["生产者 (Collection)"] Producer1 -->|"返回值 / 终止通知"| Consumer1 end subgraph "观察者模式 (Push - 生产者主导)" Producer2["生产者 (Event Stream)"] -->|"推送通知 (调用 Callback)"| Consumer2["消费者 (Observer)"] Producer2 -.->|"原生缺失的标准能力"| Missing["明确告知无更多数据 (Done)"] end ``` ### [06:01](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=361s) 关键洞见:数组与事件并无本质区别 演讲者回忆起大约五年前在微软工作时与 Erik Meijer(Rx 的缔造者)的一段关键对话。当时 Erik Meijer 抛出了一个直击本质的问题:**“数组(Array)和事件(Event)之间究竟有什么区别?”** 演讲者坦言,这个问题的答案彻底颠覆了他的认知(“absolutely blew my mind”)。这促使他极度渴望将这项革命性的技术理念带到 Netflix 这样的企业中,并在此基础上构建整套系统。经过在 Netflix 两年半的实践与验证,事实证明,只要建立起“事件与内存中的集合根本就是同一种东西”的认知,就能创造出惊人的工程成果。 为了演示后续代码,演讲者穿插了一个简短的 JavaScript 6(ES6)教程。他笑称自己之所以介绍 ES6 主要有两个原因:一是他本人就是 JavaScript 6 标准委员会成员,希望推广这项标准;二是 ES6 的箭头函数语法(Arrow Functions)能省去旧版大量繁琐的 `function` 样板代码,这样可以让幻灯片容纳更多关键逻辑。 随后,演讲者展示了一组极其灵活的基础函数,并直言:Netflix 的绝大多数异步业务代码,实际上全都是由这几个函数组合编写而成的。虽然它们初看起来全是在操作纯内存数组,似乎与异步编程风马牛不相及: - **`forEach`**:遍历集合,传入回调,对每一项执行副作用操作后结束。例如数组 `[1, 2, 3]` 经过遍历并在控制台打印,输出即为 `1, 2, 3`。 - **`map`**:投影变换,接收集合并对每一项应用映射函数,返回一个包含转换结果的全新集合。若对 `[1, 2, 3]` 的各项执行加一操作,输出即为 `[2, 3, 4]`。 - **`filter`**:过滤筛选,与 `map` 类似遍历集合,但传入的是断言函数(Predicate),用于从原始流中按条件筛选元素。 ### [08:24](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=504s) 组合基础操作符:从数组扁平化到 Netflix 的“列表的列表”(LoLo) 在回顾了数组操作的基础之后,演讲者展示了几个核心操作符如何协同工作。`filter` 接收一个返回布尔值的断言函数并应用于集合中的每一项,筛选出满足条件的元素(例如筛选出 `[2, 3]`)。接着他引入了一个关键函数——`concatAll`。虽然 JavaScript 当时并未内置该方法,也不会出现在下一代规范(ES6)中,但开发者可以非常轻松地自行实现。`concatAll` 的作用是将一个多维集合降一维打平(flatten),例如将包含嵌套数组的二维集合转换为扁平的一维集合 `[1, 2, 3, 4]`。演讲者指出,仅仅掌握 `map`、`filter` 和 `concatAll` 这三个函数,就能完成相当惊人的功能。 为了进行实际演示,演讲者以 Netflix 刚刚耗时一年打造的新版界面“Darwin UI”为例。他赞叹这个界面“非常漂亮”,并指出 Netflix 的数据模型本质上非常直观:它是一个“列表的列表”(list of list of movies),在公司内部被亲切地简称为“LoLo”。用户的界面里包含各种流派的电影行(Genre Lists),每一行内部则排列着具体影片的标题。利用这三套函数,从用户的“LoLo”中挑出所有评分为 5 星的最爱影片变得极其简单。 演讲者现场定义了函数 `getTopRatedFilms`。该函数接收一个 `user` 对象(他特别打趣提醒台下听众:“记住,箭头就是函数的意思,各位!”),随后遍历用户的视频列表集合,对其执行 `map` 操作。在映射内部,针对每个视频列表调用 `filter`,只保留评分等于 5 的电影。这步操作会产生一个由过滤后的电影集合组成的二维集合;最后,他在最外层链式调用 `concatAll` 将其打平,直接产出一个扁平的五星影片集合。随后只需对该集合调用 `forEach` 消费数据,便能优雅地完成提取。 ```mermaid flowchart TD accTitle: Netflix 电影列表处理流程 accDescr: 使用 map、filter 和 concatAll 提取并打平五星评分电影的过程 subgraph DataInput ["输入数据"] A["用户视频列表 (LoLo)"] end subgraph Processing ["数据变换"] B["map(list =>...)"] C["filter(video.rating === 5)"] D["嵌套集合 (二维数组)"] E["concatAll() 打平"] end subgraph Output ["最终消费"] F["扁平的顶级影片流"] G["forEach 遍历展示"] end A --> B B --> C C --> D D --> E E --> F F --> G ``` - `concatAll` 能将多维数组展平一维,为高阶集合操作提供了降维手段。 - Netflix 的内容呈现模型在内部被称为“LoLo”(List of Lists),非常契合函数式集合操作的场景。 --- ### [10:17](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=617s) 统一的抽象:用集合模型实现鼠标拖拽(Drag and Drop) 讲完电影数据的过滤打平后,演讲者抛出了一个让现场惊叹的设想:“如果我告诉你,我可以用近乎完全相同的代码,在 DOM 元素上写出一个鼠标拖拽事件,你会怎么说?”面对听众配合喊出的“不可能!”,演讲者笑着表示这就是他期待的反应。随后他迅速切出两套代码对比,展示了从“获取五星电影”到“捕获鼠标拖拽”的惊人相似性。 在传统的 Web 开发中,事件往往被抽象为 `addEventListener` 和 `removeEventListener` 的回调机制。但在响应式编程的世界观里,DOM 元素上的每一个事件都被视为一个随时间展开的“集合”。要定义一个鼠标拖拽(drag)事件: 1. 从某个 DOM 元素出发,使用 `map` 监听并映射该元素从现在到未来所有产生的 `mousedown` 事件集合。 2. 每次发生 `mousedown` 时,返回一个来自 `document` 级别的 `mousemove` 事件集合。 3. 之前筛选电影时使用的是 `filter`,而在这里,演讲者引入了 `takeUntil` 操作符。`takeUntil` 会持续监听源事件流,直到其参数中的“停止集合”(在这里是 `mouseup` 事件流)触发了一个元素,整个集合便立即宣布“完成”(Completed)。演讲者特意强调,这正是传统观察者模式(Observer Pattern)中缺失的关键能力——明确告知“我结束了,后续再无数据”。 4. 由于每个 `mousedown` 都映射出了一个通过 `takeUntil` 截断的 `mousemove` 集合,这同样构成了一个二维集合。紧接着应用 `concatAll` 将其展平,就得到了一条平滑代表拖拽过程中鼠标轨迹坐标的坐标点流。 最后,在这个鼠标拖拽集合上调用 `forEach`。演讲者深刻地指出,在事件驱动的世界里,“当你订阅一个事件时,你本质上是在遍历一个集合;当你添加一个事件监听器时,你所做的一切不过是在遍历集合而已。”开发者拿到发射出的坐标点更新图片位置,就能极其轻松地将元素拖拽过整个屏幕。 - 拖拽事件的核心逻辑公式化为:`mouseDowns.map(md => mouseMoves.takeUntil(mouseUps)).concatAll()`。 - `takeUntil` 提供了一种基于事件信号自动完成流的生命周期管理机制。 - 订阅(Subscribe)和添加监听器,在底层语义上完全等价于对集合的遍历(Traversing)。 --- ### [12:19](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=739s) 响应式扩展(Rx)与 Observable 类型的诞生 看到台下部分听众神情恍惚、反应不一,演讲者进一步解释了这种范式背后的技术基石:一种大约五年前才被发明出来的新集合类型——`Observable`(可观察对象)。所谓 Observable,“仅仅是一个随时间推移而展开的项的集合”。这个概念源自开源库 Reactive Extensions(Rx)。 演讲者现场做了一个简短的举手调查,发现部分参会者已经听过 Rx。他解释道,Reactive Extensions 本质上就是将开发者熟知且热爱的各种数组方法(如 `map`、`filter` 等),再加上少数特定场景的方法,全部在 `Observable` 这一全新类型上实现了一遍。Rx 最早诞生于微软并开源,支持 C#、VB.NET、JavaScript 等语言;Netflix 更是将其移植到了 Java 平台(RxJava),并在服务后台进行了极度重度的应用。 在前端 UI 开发中,界面本质上无外乎由三样东西组成:**事件(Events)**、**数据请求(Data Requests)**和**动画(Animations)**。而 Observable 恰好拥有统一为这三者建模、并将它们无缝编排组合的能力。正因如此,Observable 构成了构建 UI 极其强大的基础构件。在 Rx 中,将任何原生 DOM 事件转换为 Observable 就像调用一行 `Observable.fromEvent(element, 'mousemove')` 那样简单直截。 ```mermaid flowchart TD accTitle: Observable 抽象统一三大 UI 核心领域 accDescr: Observable 将事件、异步数据请求和动画统一在同一个流模型下 subgraph CoreAbstraction ["Observable 抽象核心"] OBS["Observable\n(随时间推移的集合)"] end subgraph UIElements ["UI 构件组成"] E["用户交互事件\n(DOM Events)"] D["异步数据请求\n(Ajax / Data Requests)"] A["动画渲染流\n(Animations)"] end subgraph Operations ["通用组合算子"] OP["map / filter / concatAll / zip / merge..."] end OBS --- E OBS --- D OBS --- A E & D & A --> OP ``` - Rx 将数组的高阶函数抽象提升到了时间序列上。 - Netflix 不仅在客户端使用 RxJS,更将 Rx 移植到了 Java 后端,全面重构服务端异步架构。 - 事件、网络请求、动画在 Observable 的视角下全部被视作同构的数据流。 --- ### [13:36](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=816s) 告别传统订阅:一等公民事件与异步错误处理 演讲者将调用 `addEventListener` 和 `removeEventListener` 的方式称为“遗留的(Legacy)事件处理方式”。以往,开发者必须在不需要数据时手动解除监听;而在 Observable 体系中,订阅过程会返回一个通用的订阅对象(Subscription Object),调用其 `dispose()` 方法即可注销。如果仅看单一事件的绑定与解绑,它与传统写法似乎差别不大,但 Observable 的真正杀伤力在于:**它将事件变成了语言中的“一等公民对象(First-class Object)”**。一旦事件成为一等公民,我们便能利用 `map`、`filter`、`reduce`、`merge`、`zip` 等丰富的方法,用极为简洁的纯函数组合出极其复杂的异步程序。 不仅如此,Observable 上的 `forEach` 还暗藏玄机。演讲者指出,以往异步编程最大的心头大患是错误处理,传统回调往往导致异常丢失或深层嵌套。而在 Observable 表达式中,管道内部任何地方抛出的错误都会沿链路向上冒泡,开发者只需在最终消费的 `forEach` 中提供一个错误回调即可一次性拦截。 正如演讲者所总结:“我们实际上在这里复刻了同步编码中 try-catch 所具备的完全相同的便利性;我不需要在每一次执行 map 或 filter 时都去检查错误,我可以等到我对那个 observable 执行 forEach 时,在最顶层去检查错误。”这种将错误与正常数据流统一管道化传播的方式,彻底解放了散落各处的错误分支。另外,Observable 还支持传递一个“完成(complete)”回调函数,在集合终结时被触发。这种“能够知道事件流何时结束”的机制,彻底改变了编写异步逻辑的底层假设。 - 传统事件监听和取消监听被抽象为统一的 `Subscription` 与 `dispose()` 接口。 - 管道内的异步异常会自动向上传播,开发者可以在终端监听点实现类似于同步代码中 `try-catch` 的集中错误捕获。 - 完成通知(Completion)补齐了异步观察者模式一直以来缺失的生命周期边界。 --- ### [15:30](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=930s) 伪语法直观演示:当数组操作符跨越到“时间”维度 为了帮助大家绕开 ES6 尚未普及带来的语法认知障碍,演讲者现场自创了一套伪代码字面量语法,以极其直观的方式对比数组与 Observable 的异同。他解释说,如果抛开技术底层,对一个 Observable 应用 `forEach`,得到的结果与对一个数组执行该操作完全一样,唯一的差异在于:“它是在时间维度上相继发生的”。 同理,`map` 应用在事件流上,就是对数据流中依次发射的每一个值应用变换函数,并沿时间轴输出转换后的新值;`filter` 也是在时间轴上依序判断条件,放行符合断言的项。演讲者强调:“因此,我们在数组上所学到的一切技能,现在都可以直接应用到事件上。” 最后,演讲者回到了异步场景中最棘手的挑战——**竞态条件(Race Conditions)**。在同步编程中,逻辑是严格按顺序一行行执行的;然而在异步领域,如果同时监听三个事件流,由于没有统一的时序约束,“任何事情都可能在同一时间爆发”,这使得保持事件处理顺序变得异常艰巨。 以往由于传统事件模型缺乏“结束(Done/Complete)”信号,开发者根本不可能为事件流写出一个通用的 `concatAll`。但现在,既然 Observable 具备了完成通知的能力,我们便能够为事件实现 `concatAll`:当第一个内部 Observable 产生数据时,外层将其打平并依次发射;当它完全结束之后,才开始消费并打平紧接着到来的第二个 Observable。演讲者借此生动地展示了数据流如何在保证时序的前提下,一个接一个地被拉平并推向管道下游。 - 响应式编程的核心认知飞跃:Observable 操作符与 Array 操作符逻辑完全一致,仅多出了“时间轴”这一维度。 - 传统的异步竞态难以协调,而具备完成态的流式抽象使得严格保序的串行扁平化算子 `concatAll` 成为可能。 ### [17:21](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1041s) 通过完成机制与 takeUntil 消除内存泄漏 讲解者继续深入解释如何按特定顺序协调事件流。当一个新的 Observable 产生时,系统并不会盲目地立即监听,而是会等待前一个 Observable 完全结束(complete)之后,才开始订阅下一个事件流。即使中间不断有新的 Observable 进入,只要当前的尚未结束,系统就会将它们缓冲(buffer)起来,按兵不动。当第二个流结束并发出数据(如数字 3)后,下一个 Observable 被订阅,如果发现里面空无一物,它就会在展平(flatten)时直接消失。正是依靠事件流能够表达“我已结束”(I'm done)这一简单语义,Rx 才能在事件流上实现 `concat`(顺序拼接)操作。 ```mermaid flowchart LR accTitle: takeUntil 工作机制 accDescr: 数据流持续向下传递数据,直到停止信号流触发事件,整个复合事件流随即宣布完成并清理订阅。 subgraph DataFlow["事件数据流"] Source["Source 数据流"] -->|"push data"| Output["下游结果流"] end subgraph SignalFlow["停止信号流"] Stop["Stop 事件流"] -->|"触发信号"| Output end Output -->|"complete() 立即终止"| Done["销毁并释放资源"] ``` 接着,讲解者兑现了前面的承诺,揭示了 `takeUntil` 的工作原理:它同时监听一个数据源集合与一个停止信号集合,只要数据源触发,数据就顺畅流向下游;一旦停止集合触发了信号,整体事件流就会立刻被标记为完成并结束。这种完成机制的核心价值在于彻底杜绝内存泄漏。讲解者幽默地打了个比方:原生的 `document.onload` 这类事件就像一个找你借钥匙去买啤酒的邻居,把啤酒交给你之后,他还尴尬地拿着钥匙站在那里不还——它明明知道自己已经完成任务了,却依然死死抓着事件订阅不放。 讲解者自豪地分享了自己的编程实践:“在过去的五年里,我从未手动取消订阅过任何事件,我的代码里也完全没有内存泄漏。”(“I haven't unsubscribed from an event in the last 5 years. I haven't, and my code's not littered with memory leaks because I don't unsubscribe from events; I complete them declaratively, I cancel events out.”)在 Netflix,工程师们不再手动去写反向注册与清理逻辑,而是声明式地定义流应该在何种条件下自然终结。回顾之前的鼠标拖拽(Mouse Drag)实现,每次 `mousedown` 触发生成新的 `mousemove` 流,都不需要担心泄漏,因为只要 `mouseup` 一发生,`mousemove` 和 `mouseup` 的监听就会被同时自动取消。 - `takeUntil` 接收源事件流与终止事件流,一旦终止流发出事件,立刻调用 `complete` 结束整体流。 - 空集合被展平时会直接消失,不会向下游传递多余数据。 - 声明式取消(Canceling events out)替代了手动管理 `removeEventListener` 或 `unsubscribe`,从根本上杜绝了因遗忘清理导致的内存泄漏。 --- ### [20:41](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1241s) 攻克 Netflix 搜索自动补全:防抖与竞态条件 讲解者将话题引向更复杂的真实业务场景:Rx 不仅能够组合普通事件流,还能将异步请求与动画有机编织在一起。以 Netflix 搜索框的自动补全(Autocomplete)为例,这表面上看似是一个“下午五点就能搞定下班”的简单任务,但实际上充满陷阱,极容易让开发者加班到深夜。自动补全的核心难点主要有两个:其一是输入抖动,当用户一口气输入 `ABCDEF` 时,程序绝不能发送六次连续的异步请求,而必须引入异步等待或防抖(debounce/throttle),等用户输入停顿后再发请求;其二是极其棘手的异步竞态条件(Race Condition)。 假设用户先输入了 `A`,系统发出请求 A;短暂等待后用户又追加输入了 `B`,系统发出针对 `AB` 的请求。网络世界并不能保证请求 A 一定比请求 AB 先返回。如果响应乱序,原本针对前缀 `A` 的旧结果很可能会覆盖掉 `AB` 的最新结果,导致界面展示完全混乱。 ```mermaid flowchart TD accTitle: 搜索自动补全事件流管道 accDescr: 按键事件经过节流处理后,发起异步请求并加入重试机制,随后被新输入截断,最后展平输出为UI可消费的结果流。 subgraph Inputs["输入处理"] Keys["按键事件 (keyPresses)"] --> Throttle["throttle / debounce 节流"] end subgraph Network["网络请求与容错"] Throttle --> Fetch["getJSON() 发起异步查询"] Fetch --> Retry["retry() 容忍偶发网络错误"] end subgraph Cancellation["竞态消除与展现"] Retry --> TakeUntilNext["takeUntil(下一次按键) 截断旧请求"] TakeUntilNext --> Flatten["展平空流并丢弃废弃数据"] Flatten --> UI["forEach 订阅并更新 UI"] end ``` 利用 Observable,这个棘手的难题得到了优雅解决: 1. **节流(Throttle)**:对按键事件流应用 `throttle`,将连续的 `A-B-C-D-E-F` 稀疏化为离散的触发点。 2. **异步映射与重试(Retry)**:每一次有效按键通过 `getJSON` 发出网络请求并返回一个 Observable。考虑到网络环境不稳定,Rx 提供了 `retry` 操作符。它的内部实现非常简明:在捕获到底层回调抛出的错误时递增计数器并重新执行,直到耗尽重试次数才向外抛错。 3. **消除竞态与展平**:当输入 `A` 的请求仍在空中飞行(in-flight)时,用户若按下了 `B`,通过对请求 A 施加 `takeUntil(下一按键)`,请求 A 的流会在产生结果前直接被标记完成并成为空集合。当外层执行展平(flatten)操作时,这个空的集合就像从未存在过一样被彻底丢弃,系统中最终只剩下针对 `AB` 的有效请求。 讲解者总结道:“我们通常习惯用简单函数组装出复杂函数,但在面对事件时却很少这样思考;而我所做的,就是将简单的按键事件和返回集合的函数组合成了我最终想要的高阶事件——即待展示的搜索结果集流。”(“I want to take small simple events and I want to compose them into more complex events. That's what I've done here: I've taken something as simple as a key press and a function that returns a collection, and I've composed it into the event that I want.”)开发者只需通过 `forEach` 消费最终的结果集更新 UI,若遇无法恢复的错误,直接在错误分支提示服务器宕机即可。 - 用户连续输入时不应发送多余请求,需通过节流机制减少服务器压力。 - 异步网络响应不可控,旧请求若迟于新请求返回会产生竞态覆盖。 - `retry` 操作符封装了计数重试逻辑,以声明式方式化解偶发网络抖动。 - 结合 `takeUntil` 与流展平机制,被废弃的空中请求变成空流自动消失,无须手写复杂的状态机去回滚状态或强制取消。 --- ### [25:00](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1500s) 重构视频播放器核心链路 随着搜索业务的成功落地,Netflix 开始将 Reactive Extensions 推向应用中更为关键和复杂的模块,其中最具代表性的便是视频播放器(Player)。在重构之前,播放器的初始化与权限鉴权逻辑深陷嵌套回调地狱(Callback Hell)。为了彻底改变这种混乱局面,团队转换了架构思维:不再将播放流程看作一连串命令式调用的执行步骤,而是将每一次播放尝试都重新定义为一个事件流集合。 当用户点击播放一部电影时,底层必须经历严密的授权认证(涉及数字版权加密和用户权限校验)。但在执行鉴权和播放之前,还有一个前置硬性依赖:底层播放器插件必须处于就绪状态。由于跨终端平台的底层播放器本身是异步初始化的,系统必须先等待初始化完成。在 Rx 的视角下,播放器就绪被抽象为一个仅包含单个元素的“初始化事件流”;系统先监听该流的完成,紧接着映射(map)到用户播放尝试(Play Attempts)事件流中,从而以清晰的流式组合取代了以往混乱的层层异步回调。 - 传统的命令式操作序列被重构为基于 Observable 的事件流集合。 - 视频播放前必须按序解决两个核心异步依赖:跨平台播放器内核的异步就绪与视频 DRM 授权认证。 - 播放器内核就绪事件被抽象为单值流,为后续整个播放鉴权与启动流提供了可组合的基础触发源。 ### [26:01](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1561s) 播放器鉴权流与异步取消机制 在播放电影时,系统会基于影片的唯一标识符调用授权接口,该接口返回一个包含播放许可(license)的 Observable。这种 Observable 与搜索结果类似,本质上是一个仅包含单项数据的异步集合,在后台完成准备后推送许可证信息。然而在现实交互中,用户随时可能点击“返回”按钮或取消操作。面对这种情况,Rx 允许开发者直接终止该数据流并将其置为空状态,无需手动管理底层网络请求的撤销,也完全不必担忧响应在不合时宜的时机返回导致状态错乱。 通过对这类二维数据集合执行展平(flatten)操作,原本离散且可能中断的请求被重组为一条平滑流动、纯粹由电影播放许可构成的事件流。鉴于每个播放初始化操作本身也是一个外层集合,再次展平后便能得到最终的授权许可流。播放器只需订阅并消费该流,直接加载许可并开始播放;倘若流程中抛出任何异常,系统也能捕获并给予告警提示,向用户说明当前无法播放。 ```mermaid flowchart TD accTitle: 播放器鉴权与许可证流处理 accDescr: 展示从影片ID发起鉴权到展平许可证流并最终交给播放器消费的异步处理流程 subgraph Auth["鉴权与流转换"] A["电影选择 (Movie ID)"] --> B["调用 authorize(movieId)"] B --> C["Observable&lt;License&gt;"] C -->|"用户点击取消"| D["终止并置空流 (Complete)"] C -->|"正常返回"| E["展平二维流 (Flatten)"] end subgraph Playback["播放控制"] E --> F["平滑的许可证流 (License Stream)"] F --> G["传递给播放器播放"] F -.->|"捕获异常"| H["提示当前无法播放"] end ``` - 面对用户触发的取消行为,系统无需繁琐的手动清理,演讲者总结道:“Well I just take that dot dot dot license and I complete it so that it's empty and I never need to worry about canceling that outgoing request or about it coming down at the wrong time and then I take this two-dimensional collection I flatten it and so now we've just got a smooth stream of movie authorizations of licenses for movies.” - 最终的消费逻辑极度精简:订阅授权流并驱动播放器工作,一旦捕获错误则统一处理为不可播放的界面提示。 --- ### [26:59](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1619s) 全栈响应式架构:从服务端到客户端的统一模型 Netflix 已经在整个应用架构中全面推行 Observable,凡是存在异步逻辑的场景几乎都能看到它的身影。在客户端,无论是应用冷启动时的多模块协同、底层平台的就绪通知,还是影片元数据的异步拉取与播放器状态调度,全部由 Observable 进行统一串联。更重要的是,这种响应式范式不仅限于前端,Netflix 在服务端同样采用了完全一致的并发模型。 这种前后端高度一致的技术选型,使开发者能够在编写客户端与服务端代码之间无缝切换。在架构实现上,服务端将各种异步任务汇聚成一个大型的 Observable,遍历其输出并将其写入响应流;客户端接收到该流后,直接将其读入为一个新的 Observable,沿着服务端处理完毕的逻辑节点无缝继续进行 `map`、`filter` 等操作符变换。此外,复杂的屏幕间过渡动画、视图与底层模型之间的数据绑定(View and Model Binding),同样全面基于 Observable 实现响应式更新。 - 服务端与客户端打通了统一的异步流处理链条,开发者在两端编写代码的思维模型完全一致。 - 界面间切换的复杂动画编排均依赖 Observable 精确协调。 - 数据绑定方案完全依赖 Observable 监听 Model 层的变化,实现向 View 层的自动响应式投递。 --- ### [27:55](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1675s) 开源生态与学习资源 演讲者分享了一系列核心学习资源,供开发者深入探索 Reactive Extensions 生态。首先是原生的 Rx 库资源,针对 JavaScript 开发者,它提供了极其强悍的异步组合能力。针对后端与企业级应用,Netflix 打造了开源项目 RxJava,不仅将响应式编程完整移植至 Java 平台,更持续探索并拓展了该模式的技术边界。 为了让新加入的工程师快速掌握这套全新的思维模式,Netflix 在内部搭建了系统的培训体系,已经成功培养了大批熟练运用 Rx 的工程师。演讲者指出,这套已被验证有效的交互式在线教学练习题已完全开源并托管在 GitHub 上,外部开发者可以完全借助与 Netflix 内部员工相同的材料来学习函数式响应式编程。演讲在演讲者对现场提问的邀请中圆满落幕:“And so you can go to this address on GitHub and use the same learning exercises this online interactive JavaScript learning exercises that we use here to teach our developers RX and uh that's about it guys questions.” - **Reactive Extensions Library**:面向前端 JavaScript 工程师的基础响应式函数库。 - **RxJava**:Netflix 主导的开源成果,将响应式扩展带入 Java 生态并持续推陈出新。 - **GitHub 交互式练习教程**:包含配套的在线 JavaScript 编程关卡,帮助开发者循序渐进掌握 map、filter、concatAll 等核心操作符。 ## Notable quotes > [02:51](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=171s) In the Observer pattern, the producer iterates you. > [17:21](https://www.youtube.com/watch?v=XRYN2xt11Ek&t=1041s) I haven't unsubscribed from an event in the last 5 years. I haven't, and my code's not littered with memory leaks because I don't unsubscribe from events; I complete them declaratively, I cancel events out. ## Mentioned Netflix, Reactive Extensions (Rx), Observable, Matt Marenghi, General Electric (GE), Microsoft, Erik Meijer, GoF (Gang of Four), Design Patterns, ECMAScript 6 (ES6), Darwin UI, RxJava, RxJS, Speed, Weezer, Blue Album, GitHub ## Video description Netflix JavaScript Talks - Episode 1 Async JavaScript with Reactive Extensions In this talk, you'll learn how Netflix uses the Reactive Extensions (Rx) library to build responsive user experiences that strive to be event-driven, scalable and resilient. Learn the power of the Observable and how we've simplified our lives through the ability to filter, aggregate, compose and project data streams. Join us while we discuss how Netflix uses Rx to: - Declaratively build complex events out of simple events (ex. drag and drop) - Coordinate and sequence multiple Ajax requests - Reactively update the UI in response to data changes - Eliminate memory leaks caused by neglecting to unsubscribe from events - Gracefully propagate and handle asynchronous exceptions [Source](https://www.youtube.com/watch?v=XRYN2xt11Ek)

Source quotes

[2:51] In the Observer pattern, the producer iterates you. [17:21] I haven't unsubscribed from an event in the last 5 years. I haven't, and my code's not littered with memory leaks because I don't unsubscribe from events; I complete them declaratively, I cancel events out.

Source quotes

[2:51] Jafar Husain: In the iterator pattern the consumer is in control and it's pulling data one item at a time until finally the iterator says I'm done. But in the observer pattern... the producer iterates you. [18:44] Jafar Husain: we don't unsubscribe, we cancel events out, we declaratively describe the conditions under which that event stream is supposed to end, and then it cleans up for us. [15:30] Jafar Husain: All the skills that we learned over arrays can now be applied to events.

Original transcript

[0:00] so I'm going to talk to you today about [0:02] our experiences with async JavaScript at [0:04] Netflix now uh all of us JavaScript [0:06] programmers have experience with async [0:08] programming um so first a little bit [0:10] about me uh I'm the cross team technical [0:12] lead for Netflix so I work across all [0:14] the various Netflix uis um the V [0:16] majority of which as Matt mentioned are [0:17] written in JavaScript I got about 13 [0:18] years in the industry formerly worked at [0:20] GE and Microsoft and I've got about six [0:22] years under my belt uh doing functional [0:24] reactive programming uh Building Systems [0:26] that is so this is really the story of [0:28] how Netflix solved big async Problems by [0:32] thinking differently about events and so [0:35] our app and in fact most JavaScript apps [0:37] are very asynchronous right we have all [0:39] these modules with app startup where [0:41] we're coordinating waiting for platform [0:42] components to come up and we're waiting [0:44] for things to come back from the web we [0:45] have the player in which you can play [0:47] and then cancel we have data access [0:49] going on at all times we've got [0:51] animations to coordinate we've got our [0:53] view to buy to a model all these things [0:55] are asynchronous processes and they can [0:57] really if you don't get them under [0:58] control they can really cause you app [1:00] complexity to just explode so let's go [1:03] back you know three years um Netflix was [1:06] really sort of struggling with async [1:09] programming as I think a lot of us were [1:10] sort of three years ago at the time we [1:12] you know we were really in callback hell [1:14] and so callback hell is this challenge [1:17] of trying to coordinate something [1:19] complex using callbacks now so this is a [1:21] sort of a contrived example but it's not [1:22] too different than what we had in our [1:24] player back then so as you can see in a [1:26] player you know why is async programming [1:28] so hard well part of it is state so if I [1:31] have two call backs that are both in [1:33] flight and I want to wait until they're [1:34] both finished well the only way to do [1:36] that is to introduce a variable and the [1:37] sort of track the state of both of those [1:39] callbacks and now you find yourself [1:41] introducing variables and variables to [1:43] track what's going on a variable to [1:44] track if I have my movie ticket yet or a [1:46] variable here to track whether my player [1:48] initialized all of a sudden it can get [1:49] really difficult to scale an app that [1:51] way because you add all this state and [1:53] that State's got to be maintained and [1:54] kept in the right well State and so [1:57] another issue one of those big issues [1:58] with async programming is that when [2:00] we're doing synchronous programming TR [2:02] catch really does a lot for us right TR [2:04] catch propagates errors we can catch it [2:06] up here but when you go into ASC [2:08] programming Tri catch are pretty much [2:10] useless and the environment provides us [2:11] no help whatsoever in sort of managing [2:14] exceptions and so how do we get back to [2:16] that same you know smoothness of dealing [2:18] with exceptions in synchronous [2:19] programming with async programming so as [2:21] you can see here we just got even more [2:22] State just to track the state of the [2:23] errors that we're having and then [2:25] finally whenever you're doing async [2:27] programming especially evented [2:28] programming there's always a risk of [2:30] leaks so I've deliberately left a little [2:31] bug in here in which I subscribe up to a [2:33] cancel button and then completely forget [2:35] to unsubscribe from that cancel button [2:37] anybody hit a memory leak like that [2:39] they're uh they're not too easy to track [2:40] down are they so we have all these [2:43] problems and you have race conditions as [2:44] well right as soon as you can do two [2:45] things at once it means you can there's [2:47] the possibility of a Race So now how did [2:51] this happen how did how did asynchronous [2:53] programming get into such a bad State [2:54] how did it get so hard well to really [2:56] answer that question you got to go back [2:57] 20 years so good year 1994 you know [3:00] speed had just come out I think Weezer's [3:03] Blue Album It Was a Very Good Year how [3:05] who has uh read this book [3:09] before you've just dated yourself people [3:11] yeah who who has the book and just like [3:13] it's just sitting on their desk they [3:15] haven't read it in a long time there you [3:16] go yeah I like that admitting it so [3:20] design patterns was a book written a [3:21] very influential book written 20 years [3:23] ago and what the the gang of four [3:25] authors they tried to do is they took a [3:26] look at all these design patterns and [3:28] all these patterns had developed in the [3:29] his for solving common problems and they [3:31] said you know what let's sit down and [3:32] let's document these and it was a very [3:34] influential book and today we're just [3:35] going to focus on sort of oh they [3:37] actually had this diagram inside that [3:39] book and one point and they really put [3:40] in an effort to try and take all these [3:41] patterns and show how they were related [3:43] to each other so you don't have to you [3:45] know look too Clos with this today we're [3:46] going be focusing on two of these [3:47] patterns these two patterns are the [3:49] iterator and observer pattern so not [3:53] every JavaScript program is familiar [3:54] with the iterator pattern but it turns [3:55] out in the next version of JavaScript [3:56] JavaScript 6 we're going to get [3:58] iterators native in JavaScript [4:00] so the iterator pattern was really the [4:02] gang of Four's way of allowing any data [4:05] structure any collection to allowing a [4:07] consumer to sort of pull data from any [4:08] particular collection no matter what [4:10] that collection is so it could be a list [4:11] it could be a set the iterator was sort [4:13] of the model was you ask the data [4:15] producer the list or whatever that data [4:17] structure is for an iterator and then [4:19] you just ask for the next item for that [4:20] iterator and you progressively just keep [4:22] getting items from that iterator until [4:24] the iterator said no more data right so [4:27] that's how iterator worked but there was [4:28] also this other p pattern and it was all [4:30] the way over here it wasn't even related [4:31] in their diagram called The Observer [4:33] pattern and the Observer pattern was [4:35] really kind of like the same thing [4:37] except the differen is this by the way [4:38] is the precursor to pubsub or event [4:40] emitter who who's done work with pubsub [4:42] either in node or right today it's [4:45] really about a producer sending data to [4:48] a consumer one item at a time [4:51] progressively right if you think about [4:52] it the iterator and the Observer pattern [4:54] aren't really that different in the [4:55] iterator pattern the consumer is in [4:57] control and it's pulling data one item [4:59] at a time until finally theator says I'm [5:01] done but in the Observer pattern what's [5:03] now become The Modern event emitter [5:05] right you give a call back to a data [5:07] producer and it calls you again and [5:10] again until finally it and that's where [5:13] something was left out they didn't [5:15] really give the Observer pattern they [5:17] didn't give the producer a way of saying [5:18] I'm done it's a little bit odd because [5:21] both these patterns as I said earlier [5:22] they're both about progressively sending [5:24] information to the consumer but in the [5:25] Observer pattern and just in Pub sub [5:27] with the vent emitter there's no [5:28] well-defined way of just saying you know [5:30] what no more data is coming to you so [5:32] that was one thing they left off well [5:34] what if we add this notion to events the [5:36] ability for an event to say you know [5:38] what I have no more data coming to you [5:40] well it turns out that we can approach [5:42] programming over you know synchronous [5:45] programming over collections in memory [5:46] collections like arrays or sets in [5:48] almost the exact same ways we can [5:49] approach event based programming so as I [5:52] said iterator pattern Observer pattern [5:54] almost the same the only difference is [5:55] uh in the Observer pattern the uh [5:57] producer iterates you right [6:01] so as I said earlier now I was working [6:03] at Microsoft about five years ago and I [6:05] met a man named Eric Meyer and he asked [6:07] me a question he said you know what's [6:08] the difference between an [6:10] array and an [6:12] event and you know the answer to that [6:14] question absolutely blew my mind I was [6:16] very excited by the technology I'm about [6:17] to show you and I was very very eager to [6:19] take it to a company like Netflix and [6:21] sort of build an entire system on it now [6:22] two and a half years later we've really [6:24] done some amazing things based on this [6:26] simple concept that events and [6:28] collections in memory collections are [6:29] really fundamentally the same [6:31] thing so as I say both collections but [6:35] first now a brief JavaScript 6 tutorial [6:38] who's familiar with what's coming up in [6:39] JavaScript 6 so I want to say this [6:42] technology has nothing to do with [6:44] JavaScript 6 the only reason I'm [6:45] teaching it to you is for two reasons [6:47] actually one I'm on the JavaScript 6 [6:49] committee and we want people to get [6:50] excited about JavaScript 6 two [6:52] JavaScript 6 allows you to write a l a [6:54] lot less code to do the same thing and [6:56] consequently it allows me to fit a lot [6:57] more code on slides so if you'll bear [6:59] with me for brief lesson it'll just take [7:00] a [7:02] second JavaScript 5 this is how you [7:04] create a function right we've all done [7:06] this starting with JavaScript 6 you can [7:09] get rid of all this [7:11] boilerplate and you can do this I'll [7:13] pause for [7:15] Applause yeah [7:18] right this is great great stuff so the [7:21] slides just got a lot shorter as you can [7:22] imagine so that's the end of the [7:24] JavaScript [7:25] tutorial you're all JavaScript six [7:28] experts now all right [7:30] so I'm about to show you guys just a few [7:33] functions and the truth is the vast [7:35] majority of Netflix's async code is [7:37] written using just these few flexible [7:39] functions and at first when I show them [7:41] to you they're going to seem to have [7:42] nothing whatsoever to do with async [7:44] programming so bear with me so the first [7:46] one we all know this this barely even [7:48] counts right we've all done this before [7:50] we can for each over an array you pass [7:51] for each a call back it calls you for [7:53] each item in that array and then we're [7:55] done so if we were to log this to the [7:56] console we would see 1 2 3 pretty [7:58] straightforward right we've done array [8:00] programming next one map quick show of [8:02] hands who's who's done map so you know [8:04] what map is so I'll speed along here [8:06] take an array apply a function to every [8:08] item in the collection and we generate a [8:10] new collection containing the translated [8:12] results so what am I going to get shout [8:13] it [8:14] out two three four okay cool I'll speed [8:17] along I can see you guys are are quick [8:19] so filter nearly the same as map except [8:22] this time that function is going to [8:24] return a Boolean that's a test we're [8:25] going to apply a test to every single [8:26] item in the collection and create a new [8:28] collection with just those items that [8:30] pass the [8:31] test Filter 2 three okay now this this [8:35] method does not include this it's not in [8:36] JavaScript and it's not going to be in [8:37] the next version of JavaScript but you [8:38] could add it pretty easily concat all [8:41] takes a multi-dimensional collection and [8:43] flattens it by one dimension so in this [8:46] case it's going to take a two- [8:46] dimensional collection flatten it we're [8:48] going to get 1 two 3 four right so quick [8:51] review map filter concat all with these [8:55] three functions I'm going to show you we [8:56] can do some pretty amazing things so we [8:59] got a lot of Netflix users here I'm sure [9:01] right this is the new Darwin UI that [9:04] we've been working on for the last year [9:05] I think it's absolutely beautiful and if [9:07] you use Netflix you know that our model [9:08] is relatively straightforward it's a [9:10] list of list of movies sometimes [9:12] affectionately referred to in the [9:13] company as a Lolo so you have a list you [9:16] have genre lists and then within that [9:18] you have titles right so why don't we [9:21] use map filter and concat all to pick [9:24] your favorite Netflix titles out of your [9:26] particular lowo right how hard can it be [9:29] so I'm going to write a function called [9:30] get top rated films and it's going to [9:33] accept a user object remember arrow [9:34] means function people right it's going [9:36] to accept a user object and I'm going to [9:38] go through the users video list I'm [9:40] going to map over them and for every [9:42] video list I'm going to return the [9:45] videos that have a rating of five so [9:48] before I return the videos I'm going to [9:49] filter the videos for only those videos [9:52] with a rating of five now that's going [9:54] to leave me with a two-dimensional [9:55] collection because for each video list [9:58] we are returning another collection of [10:00] videos which is filtered and so at the [10:02] end of that I'm just going to apply [10:03] concat all to that and flatten into a [10:05] nice flat collection of your favorite [10:07] Netflix titles and so then I'm going to [10:09] call my get top rated films on a user [10:12] and then I'm just going to for each over [10:13] the resulting collection and that's how [10:14] we get your favorite Netflix [10:17] titles so what would I what would you [10:19] say if I told you that I could take [10:20] nearly the same code and I could write a [10:22] mouse drag event over a Dom [10:28] element no way no way thank you that's [10:30] what I was waiting [10:33] for [10:36] so here's how you get your top rated [10:38] movies collection I want you to look [10:40] really carefully because it's going to [10:41] happen so quickly you might not even [10:42] notice now I'm going to switch [10:44] over here's how you get a mouse drags [10:47] collection so let's imagine that instead [10:48] of ad event listener remove event [10:50] listener every single event on a Dom [10:52] element was a collection so what is a [10:55] mouse drag event well given a Dom [10:59] element I am going to map over all of [11:02] the mouse Downs in the mouse down [11:05] collection from here onto eternity and [11:08] for each one of those Mouse Downs I will [11:09] return the mouse moves at the document [11:11] level now in the previous example I use [11:14] filter to reduce the items in the [11:15] collection to reduce the number of items [11:17] in the collection in this case I'm going [11:18] to use this new function which I'll [11:19] explain in a moment called take and till [11:21] what take and till does is it listens to [11:23] a source collection an event and it [11:25] listens to a stop collection and as soon [11:28] as that stop collection an item the [11:30] whole event collection is completed [11:33] remember that thing I said was missing [11:34] from the Observer pattern the ability to [11:36] say I'm done there's nothing more it [11:39] completes and that leaves us with a [11:40] two-dimensional collection because for [11:42] every Mouse down we get a collection of [11:44] mouse moves that we produce the number [11:46] of items of and so we've got a two [11:48] dimensional collection so we apply [11:49] concat all to flatten it out into a nice [11:51] stream of points that represent where my [11:54] mouse move is while I'm [11:57] dragging and then what do I do with this [12:00] collection I for each over it when you [12:03] subscribe up to an event you are [12:04] traversing a collection when you add an [12:06] event listener that's all you're doing [12:07] traversing a collection and so I take [12:09] that position that comes through and I [12:11] adjust the image position and now I'm [12:12] dragging an image across the [12:15] screen who's who's got it who's a little [12:19] bit shaky on this wants a little more uh [12:23] information okay let's take a look at [12:26] how's this [12:27] possible a new collection a new [12:29] collection we discovered really only 5 [12:31] years ago it's called an observable and [12:33] an observable is just a collection of [12:34] items over [12:37] time now where does this observable [12:39] comes from it comes from a library [12:41] called reactive extensions quick show of [12:42] hands who's who's heard of reactive [12:44] extensions okay so some folks um [12:46] reactive extensions is basically the [12:48] this new observable type this collection [12:49] over time and all the array functions [12:52] that you know and love plus a few more [12:53] actually they've just implemented over [12:55] observable it's open source it's been [12:57] ported to C C and VB net JavaScript and [13:00] Netflix here has actually ported it to [13:02] Java and we use it extensively on the [13:04] server so observables are capable of [13:07] modeling events data requests and [13:10] animations and because observables can [13:12] model each of these three things and [13:13] compose them together very easily you [13:15] can see how it can be very useful a very [13:17] useful building block for building uis [13:19] because what what's uis other than [13:21] events data requests and [13:23] animations so it's easy to take any Dom [13:26] event and convert it into an observable [13:27] it's just as simple as this if I want to [13:28] take the Mouse move on a dawn event and [13:30] convert into observable I just call [13:31] observable from event specify the name [13:32] of that event so you can see how if I [13:36] wanted to subscribe to an event in the [13:38] old style I just call ad event listener [13:40] I specify a Handler and then if I want [13:42] to unsubscribe I call remove event [13:43] listener event subscriptions or what I [13:46] call the Legacy way of dealing with [13:48] events the way I used to think about [13:49] events so right we add an event listener [13:52] and then we remove an event listener [13:53] when we're not interested in getting any [13:55] more data from the producer well [13:56] observables allow you to do the exact [13:57] same thing see what we got [13:59] for each over and I get back this [14:01] subscription object and if I'm not [14:03] interested in getting any more messages [14:04] from the producer I just dispose of that [14:06] subscription object so this by itself [14:08] doesn't look too different from this [14:10] right by itself it's not too too too [14:12] different but by making a first class [14:14] object for events we can use things like [14:17] map filter and reduce and merge and zip [14:18] and a host of other methods to compose [14:20] together complex asynchronous programs [14:22] with very simple functions so for each [14:26] has got a few other tricks of its sleeve [14:28] for observable so I talked earlier about [14:30] one of the challenges of air of you know [14:32] async programming being air handling so [14:34] if you have an air that occurs anywhere [14:36] within that observable expression what [14:38] happens is it gets propagated up and you [14:40] can handle it by providing a call back [14:42] for dealing with the air to the for each [14:44] method and so that Mouse drags [14:47] observable that I created earlier if it [14:48] did something like went off and to a [14:50] network and got something right and [14:51] could air well in my 4 expression I can [14:53] just specify the Handler so what have we [14:55] done here we've actually replicated the [14:56] exact same conveniences that we have [14:58] with tri match in synchronous coding I [15:00] don't have to check for erors at every [15:02] single time I do a map or a filter I can [15:04] wait until I for each over that [15:05] observable and check for erors right at [15:07] the very top and the last thing the [15:09] trick the really big trick that [15:10] observable does that your typical events [15:12] don't do is they allow you to specify a [15:14] callback that gets invoked when the [15:16] observable collection finishes and just [15:18] by adding that simple notion of being [15:20] able to stop we'll see that you can [15:22] actually approach async programming in a [15:24] very very different way and so once [15:26] again you can dispose of your [15:27] subscription and you don't want to get [15:28] any more messages anymore [15:30] so this is not in JavaScript 6 but I'm [15:33] going to make up a literal to help you [15:35] guys understand exactly how observable [15:36] works and how it sort of fits together [15:38] so not part of JavaScript 6 we're just [15:40] going to pretend that this does exist in [15:41] a fake JavaScript like language right so [15:44] if I take four each I can apply four [15:46] each to this to an observable and I get [15:48] the exact same thing as if I did it over [15:49] an array except the only difference is [15:50] it happens over time right so I get my [15:55] results right now map the same thing I [16:00] apply map to a collection an event and [16:03] it applies a function to each item that [16:04] comes out of that collection and each I [16:07] get those translated values over time [16:10] filter I think you guys are starting to [16:11] get the gist of it now right same thing [16:13] as arrays we just get it over [16:16] time so all the skills that we learned [16:19] over arrays can now be applied to events [16:23] and so concat all this is what it looks [16:25] like for arrays now one of the real [16:27] challenges that we deal with when we do [16:28] AC ASN coding is race conditions right [16:31] and when you're working with async [16:32] programming or you're you're subscribing [16:33] up to events really unlike when you're [16:35] doing synchronous programming and [16:36] everything happens one after the other [16:38] if you subscribe to three events really [16:39] anything can happen all at the same time [16:41] right and so it be very very difficult [16:43] to coordinate if you want to ensure that [16:45] you process events in in a certain order [16:47] but now that events can tell us when [16:49] they're done we can use we can write [16:51] concat all which we couldn't have [16:53] written over events before we can now [16:54] write concat all over events so if I [16:57] apply a concat all to a collection of [17:00] collections an event of event streams it [17:03] looks something like this well we have [17:05] this observable that over time comes [17:06] along with another observable and we [17:08] start listening to it and we immediately [17:09] get the value out and then another [17:11] observable comes along and it fires two [17:14] and we immediately flatten that and just [17:16] push that value out now while we're [17:19] waiting for three to come along another [17:21] observable comes out but instead of [17:22] immediately just listening to that I'm [17:24] going to wait until this observable [17:26] completes before I even start listening [17:29] to this event stream and this is how we [17:31] control events to make sure that they [17:32] happen or are processed in the order in [17:34] which we want to process them and so I [17:36] just keep buffer another observable [17:37] comes in I still haven't gotten three I [17:39] just buffer it up I just hold on to that [17:40] event stream waiting to subscribe to it [17:43] now when the second one ends we push [17:46] three through then we start listening to [17:49] this observable we find that there's [17:50] nothing inside now I want to point out [17:53] empty collections when they're flattened [17:55] they just disappear and this is very [17:57] very useful and we'll see why in a [17:58] moment so we get no data out of this and [18:01] then finally we process four but we make [18:03] sure that four appears after three and [18:07] so just by adding that simple semantic [18:08] to event streams being able to say I'm [18:10] done we can now Implement concat over [18:12] event streams and so let's see what we [18:15] can do so earlier I promised you guys I [18:17] was going to explain how take and till [18:18] works it's actually really simple we [18:19] just listen to one event stream until [18:21] another event stream fires and then we [18:23] just complete the overall event stream [18:25] so take till just like map and filter [18:27] what we're doing is we're creating a new [18:28] collection from other collections so [18:30] we're listening to a source of data and [18:34] we're listening to a stop collection and [18:36] whenever this Source collection fires we [18:38] just push the data on through until [18:40] finally the stop collection fires and [18:42] then we just complete the event so why [18:44] is it so useful to be able to complete [18:46] an event stream to have this notion of [18:47] I'm done Beyond just concat all anybody [18:50] ever I mentioned this earlier anybody [18:51] ever have an event subscription they [18:53] forgot to unsubscribe and they they [18:55] leaked [18:56] memory that's kind of silly in some [18:59] situations because there are some events [19:01] that definitely end document onload for [19:03] example is an example of an event that [19:05] ends but it's sort of like a friendly [19:07] neighbor who like borrows your keys to [19:09] go get some beer and then he he brings [19:11] you back the beer and he just sort of [19:12] stands there awkwardly with your keys [19:14] you know until you're like can I have my [19:16] keys back you know that's really what [19:19] document onload does it holds on to your [19:21] subscription even though it knows it's [19:22] it's already given you the message and [19:23] it's done by allowing observables or [19:26] events to say I'm done you know what [19:30] they can just free your subscription at [19:31] the same time I haven't unsubscribed for [19:33] an event in The Last 5 Years I [19:36] haven't and my code's not littered with [19:39] memory with with with uh with memory [19:41] leaks because I don't unsubscribe from [19:44] events I complete them declaratively I [19:47] cancel events out so if you're listening [19:50] to an event stream instead of [19:52] registering with another event and when [19:53] that event fires unsubscribing from the [19:55] previous event and then remembering to [19:56] unsubscribe from that event I just take [19:58] those two events and I cancel them out [20:00] and that's why it's so powerful that's [20:01] why we think about events very very [20:03] differently here at Netflix we don't [20:05] unsubscribe we cancel events out we [20:07] declaratively describe the conditions [20:09] under which that event stream is [20:10] supposed to end and then it cleans up [20:11] for [20:12] us so let's take a look at our Mouse [20:15] drags collection now that we know how [20:16] all these methods work we should be able [20:17] to understand this it's relatively [20:18] straightforward we keep generating this [20:20] new mouse moves collection every single [20:22] time a mouse down happens but we don't [20:23] have to worry about unsubscribing from [20:25] it because as soon as the mouse UPS [20:27] fires we unsubscribe from both Mouse [20:28] moves and mouse UPS we never unsubscribe [20:30] for Mouse Downs because somebody could [20:31] start a drag operation or any given time [20:34] right people understand this a little [20:36] bit better now they've seen how [20:38] observable [20:39] works [20:41] okay so let's talk about a more complex [20:43] example because I promise you [20:44] observables are able to compose not just [20:46] event streams but asynchronous requests [20:48] and animations and when you put the [20:50] three together it's actually pretty cool [20:51] it's really breathtaking so Netflix [20:53] search is a great example of a a [20:56] surprisingly hard problem who's written [20:57] an autocomplete box [21:00] before one of those problems it seems [21:02] like it's going to be pretty easy right [21:04] at first you're like I'll be out here at [21:05] 5: and doesn't really turn out that way [21:08] right so why is an autocomplete box so [21:11] damn hard it's because right well the [21:13] first problem the first place where [21:15] asynchron enters the the the problem is [21:17] if I type ABCDE EF how many requests do [21:19] I want to send [21:22] off thanks Mike one request right I [21:25] don't want to send off six requests back [21:26] to back and so we we in a little [21:29] asynchrony or we debounce as it's [21:31] sometimes called we want to wait hang [21:32] back a little bit wait till things [21:33] settle down before we send off a request [21:35] so we want to introduce asynchron to [21:37] make sure we've got a good user [21:38] experience the second problem is a much [21:40] much harder problem to solve and it's [21:41] very common in async programming so [21:43] let's say I type A and then we wait our [21:46] 20 milliseconds or 30 milliseconds and [21:48] then I type B and so we've got a request [21:49] out going for a and now we've got a [21:51] request out going for a is there any [21:54] guarantee my request for a is going to [21:55] come back before my request for a [21:58] no it's a race condition and so we can [22:01] end up showing the results for a on top [22:03] of the results for ab right and then [22:05] you're there until 9:00 trying to figure [22:07] out what's going on so how do we solve [22:09] this problem well observable by creating [22:12] an object on which we can hang off these [22:14] methods that you know handle these [22:16] common asynchronous patterns we can [22:18] solve this very very easily so I can [22:20] take the key presses collection because [22:23] that's how I think about events I think [22:24] of them as collections now I think of [22:26] that collection and I apply this simple [22:28] throttle method and all that does is it [22:29] takes a b c d e f and it turns it into a [22:33] d f and then I math it for every key I [22:37] call this get Json method and I go off [22:39] and I get this observable and that [22:42] returns an observable it returns a [22:43] collection containing only one thing [22:45] which is an array of those search [22:46] results it's just going to be a [22:47] collection that after a little while [22:49] gives me an array of the search results [22:52] now bis can fail right Every Time We [22:54] Touch the network we can have a failure [22:56] and so it could just be an intermittent [22:58] Network fail so I wouldn't want to bring [22:59] down the whole thing if we just have [23:01] some small intermittent failure right [23:03] and so I use the retry method and what [23:04] retry does is so simple it just four [23:06] each is over an observable and if it [23:08] gets the error call back to 4 each it [23:10] increments a counter 4 each is over it [23:12] again if it gets an error again [23:14] increments a counter until it exhausts [23:15] the retry count and then it just [23:17] forwards the err along and so dealing [23:20] with intermittent failures in the [23:21] network is see simple as [23:23] this then what happens if I press so [23:27] I've got my outgoing PR for a right it's [23:29] going out and then I press B how do I [23:31] make sure that that outgoing request for [23:32] a doesn't return me anything because I [23:34] don't care about it anymore right I just [23:35] want the outgoing request for ab to to [23:37] return something well once I request [23:39] create this request for a it's it's [23:42] inflight it's an observable that's sort [23:43] of dot dot dot dot dot as soon as I [23:45] press B what [23:47] happens by applying take and to that [23:50] outgoing request I just complete the [23:52] collection before it can ever return [23:54] results and now at the end when I [23:57] flatten what happens when you flatten an [23:59] empty collection in another [24:03] collection disappears so we don't I [24:05] don't think about this is a state [24:06] machine of rolling back States or [24:08] canceling requests I just complete a [24:10] collection and it's empty and I flatten [24:13] and then it just goes away and now the [24:15] only thing outgoing is my second request [24:17] for [24:18] ab at the end I've create I've taken the [24:21] simple collections that I have in the [24:23] system and I've created more complex [24:24] collections out of them we do this all [24:25] the time with functions I have simple [24:26] functions I create more complex function [24:28] functions for some reason when we move [24:29] to events we don't think this way I want [24:31] to take small simple events and I want [24:33] to compose them into more Complex events [24:35] that's what I've done here I've taken [24:36] something as simple as a key press and a [24:38] function that returns a collection and [24:39] I've composed it into the event that I [24:41] want the high level event which is just [24:43] a stream of all the search result sets [24:44] I'm supposed to [24:45] display and then for each one of those [24:47] search result sets I just update the UI [24:49] inside of my for each statement and if [24:52] an error comes I say hey sorry the [24:54] server's [24:55] down and that's auto complete with RX [25:00] so we didn't stop with uh search we've [25:02] moved RX to so many different parts of [25:04] the Netflix app and one one of the ones [25:06] I mentioned earlier was the player [25:07] player that call back hell that you saw [25:10] earlier uh we can do a lot better than [25:13] this if we recast we think about instead [25:16] of just you know this series of [25:17] operations that we perform if we think [25:19] about every single play as just a stream [25:22] a stream of you know just a collection [25:24] we can approach this problem very very [25:27] differently so really what we're after [25:29] is when you start to try and play a [25:30] movie what Netflix will do is it'll go [25:31] and try and authorize you right there's [25:33] encryption and there's there's [25:34] permissions and so we need to make sure [25:36] that you're authorized to play that [25:37] movie and so the very first thing we [25:38] need to do before we do any of that is [25:39] we need to make sure that the player is [25:40] even initialized we're working with a [25:42] platform and that player comes up [25:44] asynchronously and so we need to wait [25:45] for it to initialize and that's as [25:46] simple as listening for all the [25:49] initializations in the initialization [25:51] stream which only ever contains one item [25:52] in that collection mapping over it and [25:55] then listening for all the attempts to [25:56] play a movie [25:58] for each one of those movie attempts [26:00] those play those attempts to play a [26:01] movie we're going to call authorize on [26:03] that movie ID and that's going to go off [26:05] and return an observable of a license so [26:07] an observable just like the search [26:08] results said an observable that contains [26:10] only one thing and it's going to go dot [26:12] dot dot dot and then give us a license [26:14] but in the meantime what if they cancel [26:17] what if somebody cancels because they [26:18] hit the back button or whatever else [26:20] well I just take that dot dot dot [26:22] license and I complete it so that it's [26:24] empty and I never need to worry about [26:26] canceling that outgoing request or about [26:28] it coming down at the wrong time and [26:30] then I take this two-dimensional [26:32] collection I flatten it and so now we've [26:34] just got a smooth stream of movie [26:36] authorizations of licenses for movies [26:38] and then I take this outer collection [26:39] because we're doing it for every [26:40] initialization which was only one and [26:42] then I flatten that and we're left with [26:45] just a stream of authorizations to play [26:47] a movie and all I do is I just reverse [26:50] that stream I tell the player to play [26:52] the license and if an error happens we [26:54] say that we can't play right now so [26:57] that's how we apply to player [26:59] um so today we use observables [27:01] everywhere we use observables for really [27:04] any part of our app that has any kind of [27:05] async code in it for coordinating all [27:07] the things that are going on in app [27:08] startup for platform coming up for [27:10] metadata coming down we use observables [27:12] for the player we use observables we use [27:13] observables for our data access not just [27:15] on the client but we actually use [27:16] observables on the server as well so our [27:19] you know our our developers can [27:20] transition very smoothly from writing RX [27:23] observable code on the client to then [27:24] transition to the server and use the [27:26] exact same concurrency model there in [27:28] fact on the server we create one big [27:29] observable and then we for each over it [27:31] and push it out to the stream and then [27:32] on the client we read it into an [27:34] observable and just keep on mapping and [27:36] filtering where we left off so [27:39] animations coordinated today with [27:41] observables so some of the demos you're [27:43] going to see really impressive demos of [27:44] our of our animations moving from screen [27:46] to screen are all coordinated with [27:47] observables and View and model minding [27:50] ensuring that the view is updated when [27:51] our model changes that type of data [27:53] binding is all done with observable as [27:55] well so here are some of the resources I [27:57] want share with you the first is the [27:59] link to the actual reactive extensions [28:01] Library so if you a JavaScript [28:02] programmer check this out it's [28:04] absolutely amazing stuff um the next is [28:06] rx Java if you do any Java programming [28:08] this is Netflix's open source effort at [28:10] porting RX to Java and the truth is not [28:12] only we have we ported it we're doing [28:13] some really amazing new things we're [28:15] pushing the edges of what RX can do [28:16] right now uh and we have an internal [28:18] training program for teaching all of our [28:20] developers RX and we've taught tons and [28:22] tons of developers now to use RX [28:24] effectively and so you can go to this [28:26] address on GitHub and use the same [28:28] learning exercises this online [28:30] interactive JavaScript learning [28:31] exercises that we use here to teach our [28:33] developers RX and uh that's about it [28:37] guys questions