diff --git a/doc/observable.md b/doc/observable.md index 8afa1f35d8..8e8cc22df9 100644 --- a/doc/observable.md +++ b/doc/observable.md @@ -9,8 +9,10 @@ Observables are lazy Push collections of multiple values. They fill the missing **Example.** The following is an Observable that pushes the values `1`, `2`, `3` immediately (synchronously) when subscribed, and the value `4` after one second has passed since the subscribe call, then completes: -```js -var observable = Rx.Observable.create(function (observer) { +```ts +import { Observable } from 'rxjs'; + +const observable = Observable.create(function (observer) { observer.next(1); observer.next(2); observer.next(3); @@ -23,8 +25,10 @@ var observable = Rx.Observable.create(function (observer) { To invoke the Observable and see these values, we need to *subscribe* to it: -```js -var observable = Rx.Observable.create(function (observer) { +```ts +import { Observable } from 'rxjs'; + +const observable = Observable.create(function (observer) { observer.next(1); observer.next(2); observer.next(3); @@ -90,15 +94,15 @@ Contrary to popular claims, Observables are not like EventEmitters nor are they Consider the following: -```js +```ts function foo() { console.log('Hello'); return 42; } -var x = foo.call(); // same as foo() +const x = foo.call(); // same as foo() console.log(x); -var y = foo.call(); // same as foo() +const y = foo.call(); // same as foo() console.log(y); ``` @@ -113,8 +117,10 @@ We expect to see as output: You can write the same behavior above, but with Observables: -```js -var foo = Rx.Observable.create(function (observer) { +```ts +import { Observable } from 'rxjs'; + +const foo = Observable.create(function (observer) { console.log('Hello'); observer.next(42); }); @@ -142,7 +148,6 @@ This happens because both functions and Observables are lazy computations. If yo Some people claim that Observables are asynchronous. That is not true. If you surround a function call with logs, like this: - ```js console.log('before'); console.log(foo.call()); @@ -160,7 +165,6 @@ You will see the output: And this is the same behavior with Observables: - ```js console.log('before'); foo.subscribe(function (x) { @@ -194,8 +198,10 @@ function foo() { Functions can only return one value. Observables, however, can do this: -```js -var foo = Rx.Observable.create(function (observer) { +```ts +import { Observable } from 'rxjs'; + +const foo = Observable.create(function (observer) { console.log('Hello'); observer.next(42); observer.next(100); // "return" another value @@ -222,8 +228,10 @@ With synchronous output: But you can also "return" values asynchronously: -```js -var foo = Rx.Observable.create(function (observer) { +```ts +import { Observable } from 'rxjs'; + +const foo = Observable.create(function (observer) { console.log('Hello'); observer.next(42); observer.next(100); @@ -259,7 +267,7 @@ Conclusion: ## Anatomy of an Observable -Observables are **created** using `Rx.Observable.create` or a creation operator, are **subscribed** to with an Observer, **execute** to deliver `next` / `error` / `complete` notifications to the Observer, and their execution may be **disposed**. These four aspects are all encoded in an Observable instance, but some of these aspects are related to other types, like Observer and Subscription. +Observables are **created** using `Observable.create` or a creation operator, are **subscribed** to with an Observer, **execute** to deliver `next` / `error` / `complete` notifications to the Observer, and their execution may be **disposed**. These four aspects are all encoded in an Observable instance, but some of these aspects are related to other types, like Observer and Subscription. Core Observable concerns: - **Creating** Observables @@ -269,13 +277,15 @@ Core Observable concerns: ### Creating Observables -`Rx.Observable.create` is an alias for the `Observable` constructor, and it takes one argument: the `subscribe` function. +`Observable.create` is an alias for the `Observable` constructor, and it takes one argument: the `subscribe` function. The following example creates an Observable to emit the string `'hi'` every second to an Observer. -```js -var observable = Rx.Observable.create(function subscribe(observer) { - var id = setInterval(() => { +```ts +import { Observable } from 'rxjs'; + +const observable = Observable.create(function subscribe(observer) { + const id = setInterval(() => { observer.next('hi') }, 1000); }); @@ -289,8 +299,7 @@ In the example above, the `subscribe` function is the most important piece to de The Observable `observable` in the example can be *subscribed* to, like this: - -```js +```ts observable.subscribe(x => console.log(x)); ``` @@ -326,8 +335,10 @@ next*(error|complete)? The following is an example of an Observable execution that delivers three Next notifications, then completes: -```js -var observable = Rx.Observable.create(function subscribe(observer) { +```ts +import { Observable } from 'rxjs'; + +const observable = Observable.create(function subscribe(observer) { observer.next(1); observer.next(2); observer.next(3); @@ -337,8 +348,10 @@ var observable = Rx.Observable.create(function subscribe(observer) { Observables strictly adhere to the Observable Contract, so the following code would not deliver the Next notification `4`: -```js -var observable = Rx.Observable.create(function subscribe(observer) { +```ts +import { Observable } from 'rxjs'; + +const observable = Observable.create(function subscribe(observer) { observer.next(1); observer.next(2); observer.next(3); @@ -349,8 +362,10 @@ var observable = Rx.Observable.create(function subscribe(observer) { It is a good idea to wrap any code in `subscribe` with `try`/`catch` block that will deliver an Error notification if it catches an exception: -```js -var observable = Rx.Observable.create(function subscribe(observer) { +```ts +import { Observable } from 'rxjs'; + +const observable = Observable.create(function subscribe(observer) { try { observer.next(1); observer.next(2); @@ -368,16 +383,17 @@ Because Observable Executions may be infinite, and it's common for an Observer t When `observable.subscribe` is called, the Observer gets attached to the newly created Observable execution. This call also returns an object, the `Subscription`: - -```js -var subscription = observable.subscribe(x => console.log(x)); +```ts +const subscription = observable.subscribe(x => console.log(x)); ``` -The Subscription represents the ongoing execution, and has a minimal API which allows you to cancel that execution. Read more about the [`Subscription` type here](./overview.html#subscription). With `subscription.unsubscribe()` you can cancel the ongoing execution: +The Subscription represents the ongoing execution, and has a minimal API which allows you to cancel that execution. Read more about the [`Subscription` type here](./subscription). With `subscription.unsubscribe()` you can cancel the ongoing execution: -```js -var observable = Rx.Observable.from([10, 20, 30]); -var subscription = observable.subscribe(x => console.log(x)); +```ts +import { Observable } from 'rxjs'; + +const observable = Observable.from([10, 20, 30]); +const subscription = observable.subscribe(x => console.log(x)); // Later: subscription.unsubscribe(); ``` diff --git a/doc/subscription.md b/doc/subscription.md index f7d0ab1e29..55616381d5 100644 --- a/doc/subscription.md +++ b/doc/subscription.md @@ -2,9 +2,11 @@ **What is a Subscription?** A Subscription is an object that represents a disposable resource, usually the execution of an Observable. A Subscription has one important method, `unsubscribe`, that takes no argument and just disposes the resource held by the subscription. In previous versions of RxJS, Subscription was called "Disposable". -```js -var observable = Rx.Observable.interval(1000); -var subscription = observable.subscribe(x => console.log(x)); +```ts +import { interval } from 'rxjs'; + +const observable = interval(1000); +const subscription = observable.subscribe(x => console.log(x)); // Later: // This cancels the ongoing Observable execution which // was started by calling subscribe with an Observer. @@ -15,12 +17,14 @@ subscription.unsubscribe(); Subscriptions can also be put together, so that a call to an `unsubscribe()` of one Subscription may unsubscribe multiple Subscriptions. You can do this by "adding" one subscription into another: -```js -var observable1 = Rx.Observable.interval(400); -var observable2 = Rx.Observable.interval(300); +```ts +import { interval } from 'rxjs'; + +const observable1 = interval(400); +const observable2 = interval(300); -var subscription = observable1.subscribe(x => console.log('first: ' + x)); -var childSubscription = observable2.subscribe(x => console.log('second: ' + x)); +const subscription = observable1.subscribe(x => console.log('first: ' + x)); +const childSubscription = observable2.subscribe(x => console.log('second: ' + x)); subscription.add(childSubscription); diff --git a/docs_app/content/guide/observable.md b/docs_app/content/guide/observable.md new file mode 100644 index 0000000000..90d80ad485 --- /dev/null +++ b/docs_app/content/guide/observable.md @@ -0,0 +1,440 @@ +# Observable + +Observables are lazy Push collections of multiple values. They fill the missing spot in the following table: + +| | Single | Multiple | +| --- | --- | --- | +| **Pull** | [`Function`](https://developer.mozilla.org/en-US/docs/Glossary/Function) | [`Iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) | +| **Push** | [`Promise`](https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Promise) | [`Observable`](../class/es6/Observable.js~Observable.html) | + +**Example.** The following is an Observable that pushes the values `1`, `2`, `3` immediately (synchronously) when subscribed, and the value `4` after one second has passed since the subscribe call, then completes: + +```ts +import { Observable } from 'rxjs'; + +const observable = Observable.create(function (observer) { + observer.next(1); + observer.next(2); + observer.next(3); + setTimeout(() => { + observer.next(4); + observer.complete(); + }, 1000); +}); +``` + +To invoke the Observable and see these values, we need to *subscribe* to it: + +```ts +import { Observable } from 'rxjs'; + +const observable = Observable.create(function (observer) { + observer.next(1); + observer.next(2); + observer.next(3); + setTimeout(() => { + observer.next(4); + observer.complete(); + }, 1000); +}); + +console.log('just before subscribe'); +observable.subscribe({ + next: x => console.log('got value ' + x), + error: err => console.error('something wrong occurred: ' + err), + complete: () => console.log('done'), +}); +console.log('just after subscribe'); +``` + +Which executes as such on the console: + +```none +just before subscribe +got value 1 +got value 2 +got value 3 +just after subscribe +got value 4 +done +``` + +## Pull versus Push + +*Pull* and *Push* are two different protocols that describe how a data *Producer* can communicate with a data *Consumer*. + +**What is Pull?** In Pull systems, the Consumer determines when it receives data from the data Producer. The Producer itself is unaware of when the data will be delivered to the Consumer. + +Every JavaScript Function is a Pull system. The function is a Producer of data, and the code that calls the function is consuming it by "pulling" out a *single* return value from its call. + +ES2015 introduced [generator functions and iterators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*) (`function*`), another type of Pull system. Code that calls `iterator.next()` is the Consumer, "pulling" out *multiple* values from the iterator (the Producer). + + +| | Producer | Consumer | +| --- | --- | --- | +| **Pull** | **Passive:** produces data when requested. | **Active:** decides when data is requested. | +| **Push** | **Active:** produces data at its own pace. | **Passive:** reacts to received data. | + +**What is Push?** In Push systems, the Producer determines when to send data to the Consumer. The Consumer is unaware of when it will receive that data. + +Promises are the most common type of Push system in JavaScript today. A Promise (the Producer) delivers a resolved value to registered callbacks (the Consumers), but unlike functions, it is the Promise which is in charge of determining precisely when that value is "pushed" to the callbacks. + +RxJS introduces Observables, a new Push system for JavaScript. An Observable is a Producer of multiple values, "pushing" them to Observers (Consumers). + +- A **Function** is a lazily evaluated computation that synchronously returns a single value on invocation. +- A **generator** is a lazily evaluated computation that synchronously returns zero to (potentially) infinite values on iteration. +- A **Promise** is a computation that may (or may not) eventually return a single value. +- An **Observable** is a lazily evaluated computation that can synchronously or asynchronously return zero to (potentially) infinite values from the time it's invoked onwards. + +## Observables as generalizations of functions + +Contrary to popular claims, Observables are not like EventEmitters nor are they like Promises for multiple values. Observables *may act* like EventEmitters in some cases, namely when they are multicasted using RxJS Subjects, but usually they don't act like EventEmitters. + +Observables are like functions with zero arguments, but generalize those to allow multiple values. + +Consider the following: + +```ts +function foo() { + console.log('Hello'); + return 42; +} + +const x = foo.call(); // same as foo() +console.log(x); +const y = foo.call(); // same as foo() +console.log(y); +``` + +We expect to see as output: + +```none +"Hello" +42 +"Hello" +42 +``` + +You can write the same behavior above, but with Observables: + +```ts +import { Observable } from 'rxjs'; + +const foo = Observable.create(function (observer) { + console.log('Hello'); + observer.next(42); +}); + +foo.subscribe(function (x) { + console.log(x); +}); +foo.subscribe(function (y) { + console.log(y); +}); +``` + +And the output is the same: + +```none +"Hello" +42 +"Hello" +42 +``` + +This happens because both functions and Observables are lazy computations. If you don't call the function, the `console.log('Hello')` won't happen. Also with Observables, if you don't "call" it (with `subscribe`), the `console.log('Hello')` won't happen. Plus, "calling" or "subscribing" is an isolated operation: two function calls trigger two separate side effects, and two Observable subscribes trigger two separate side effects. As opposed to EventEmitters which share the side effects and have eager execution regardless of the existence of subscribers, Observables have no shared execution and are lazy. + +Subscribing to an Observable is analogous to calling a Function. + +Some people claim that Observables are asynchronous. That is not true. If you surround a function call with logs, like this: + +```js +console.log('before'); +console.log(foo.call()); +console.log('after'); +``` + +You will see the output: + +```none +"before" +"Hello" +42 +"after" +``` + +And this is the same behavior with Observables: + +```js +console.log('before'); +foo.subscribe(function (x) { + console.log(x); +}); +console.log('after'); +``` + +And the output is: + +```none +"before" +"Hello" +42 +"after" +``` + +Which proves the subscription of `foo` was entirely synchronous, just like a function. + +Observables are able to deliver values either synchronously or asynchronously. + +What is the difference between an Observable and a function? **Observables can "return" multiple values over time**, something which functions cannot. You can't do this: + +```js +function foo() { + console.log('Hello'); + return 42; + return 100; // dead code. will never happen +} +``` + +Functions can only return one value. Observables, however, can do this: + +```ts +import { Observable } from 'rxjs'; + +const foo = Observable.create(function (observer) { + console.log('Hello'); + observer.next(42); + observer.next(100); // "return" another value + observer.next(200); // "return" yet another +}); + +console.log('before'); +foo.subscribe(function (x) { + console.log(x); +}); +console.log('after'); +``` + +With synchronous output: + +```none +"before" +"Hello" +42 +100 +200 +"after" +``` + +But you can also "return" values asynchronously: + +```ts +import { Observable } from 'rxjs'; + +const foo = Observable.create(function (observer) { + console.log('Hello'); + observer.next(42); + observer.next(100); + observer.next(200); + setTimeout(() => { + observer.next(300); // happens asynchronously + }, 1000); +}); + +console.log('before'); +foo.subscribe(function (x) { + console.log(x); +}); +console.log('after'); +``` + +With output: + +```none +"before" +"Hello" +42 +100 +200 +"after" +300 +``` + +Conclusion: + +- `func.call()` means "*give me one value synchronously*" +- `observable.subscribe()` means "*give me any amount of values, either synchronously or asynchronously*" + +## Anatomy of an Observable + +Observables are **created** using `Observable.create` or a creation operator, are **subscribed** to with an Observer, **execute** to deliver `next` / `error` / `complete` notifications to the Observer, and their execution may be **disposed**. These four aspects are all encoded in an Observable instance, but some of these aspects are related to other types, like Observer and Subscription. + +Core Observable concerns: +- **Creating** Observables +- **Subscribing** to Observables +- **Executing** the Observable +- **Disposing** Observables + +### Creating Observables + +`Observable.create` is an alias for the `Observable` constructor, and it takes one argument: the `subscribe` function. + +The following example creates an Observable to emit the string `'hi'` every second to an Observer. + +```ts +import { Observable } from 'rxjs'; + +const observable = Observable.create(function subscribe(observer) { + const id = setInterval(() => { + observer.next('hi') + }, 1000); +}); +``` + +Observables can be created with `create`, but usually we use the so-called [creation operators](./overview.html#creation-operators), like `of`, `from`, `interval`, etc. + +In the example above, the `subscribe` function is the most important piece to describe the Observable. Let's look at what subscribing means. + +### Subscribing to Observables + +The Observable `observable` in the example can be *subscribed* to, like this: + +```ts +observable.subscribe(x => console.log(x)); +``` + +It is not a coincidence that `observable.subscribe` and `subscribe` in `Observable.create(function subscribe(observer) {...})` have the same name. In the library, they are different, but for practical purposes you can consider them conceptually equal. + +This shows how `subscribe` calls are not shared among multiple Observers of the same Observable. When calling `observable.subscribe` with an Observer, the function `subscribe` in `Observable.create(function subscribe(observer) {...})` is run for that given Observer. Each call to `observable.subscribe` triggers its own independent setup for that given Observer. + +Subscribing to an Observable is like calling a function, providing callbacks where the data will be delivered to. + +This is drastically different to event handler APIs like `addEventListener` / `removeEventListener`. With `observable.subscribe`, the given Observer is not registered as a listener in the Observable. The Observable does not even maintain a list of attached Observers. + +A `subscribe` call is simply a way to start an "Observable execution" and deliver values or events to an Observer of that execution. + +### Executing Observables + +The code inside `Observable.create(function subscribe(observer) {...})` represents an "Observable execution", a lazy computation that only happens for each Observer that subscribes. The execution produces multiple values over time, either synchronously or asynchronously. + +There are three types of values an Observable Execution can deliver: + +- "Next" notification: sends a value such as a Number, a String, an Object, etc. +- "Error" notification: sends a JavaScript Error or exception. +- "Complete" notification: does not send a value. + +Next notifications are the most important and most common type: they represent actual data being delivered to an Observer. Error and Complete notifications may happen only once during the Observable Execution, and there can only be either one of them. + +These constraints are expressed best in the so-called *Observable Grammar* or *Contract*, written as a regular expression: + +```none +next*(error|complete)? +``` + +In an Observable Execution, zero to infinite Next notifications may be delivered. If either an Error or Complete notification is delivered, then nothing else can be delivered afterwards. + +The following is an example of an Observable execution that delivers three Next notifications, then completes: + +```ts +import { Observable } from 'rxjs'; + +const observable = Observable.create(function subscribe(observer) { + observer.next(1); + observer.next(2); + observer.next(3); + observer.complete(); +}); +``` + +Observables strictly adhere to the Observable Contract, so the following code would not deliver the Next notification `4`: + +```ts +import { Observable } from 'rxjs'; + +const observable = Observable.create(function subscribe(observer) { + observer.next(1); + observer.next(2); + observer.next(3); + observer.complete(); + observer.next(4); // Is not delivered because it would violate the contract +}); +``` + +It is a good idea to wrap any code in `subscribe` with `try`/`catch` block that will deliver an Error notification if it catches an exception: + +```ts +import { Observable } from 'rxjs'; + +const observable = Observable.create(function subscribe(observer) { + try { + observer.next(1); + observer.next(2); + observer.next(3); + observer.complete(); + } catch (err) { + observer.error(err); // delivers an error if it caught one + } +}); +``` + +### Disposing Observable Executions + +Because Observable Executions may be infinite, and it's common for an Observer to want to abort execution in finite time, we need an API for canceling an execution. Since each execution is exclusive to one Observer only, once the Observer is done receiving values, it has to have a way to stop the execution, in order to avoid wasting computation power or memory resources. + +When `observable.subscribe` is called, the Observer gets attached to the newly created Observable execution. This call also returns an object, the `Subscription`: + +```ts +const subscription = observable.subscribe(x => console.log(x)); +``` + +The Subscription represents the ongoing execution, and has a minimal API which allows you to cancel that execution. Read more about the [`Subscription` type here](./guide/subscription). With `subscription.unsubscribe()` you can cancel the ongoing execution: + +```ts +import { Observable } from 'rxjs'; + +const observable = Observable.from([10, 20, 30]); +const subscription = observable.subscribe(x => console.log(x)); +// Later: +subscription.unsubscribe(); +``` + +When you subscribe, you get back a Subscription, which represents the ongoing execution. Just call `unsubscribe()` to cancel the execution. + +Each Observable must define how to dispose resources of that execution when we create the Observable using `create()`. You can do that by returning a custom `unsubscribe` function from within `function subscribe()`. + +For instance, this is how we clear an interval execution set with `setInterval`: + +```js +var observable = Rx.Observable.create(function subscribe(observer) { + // Keep track of the interval resource + var intervalID = setInterval(() => { + observer.next('hi'); + }, 1000); + + // Provide a way of canceling and disposing the interval resource + return function unsubscribe() { + clearInterval(intervalID); + }; +}); +``` + +Just like `observable.subscribe` resembles `Observable.create(function subscribe() {...})`, the `unsubscribe` we return from `subscribe` is conceptually equal to `subscription.unsubscribe`. In fact, if we remove the ReactiveX types surrounding these concepts, we're left with rather straightforward JavaScript. + +```js +function subscribe(observer) { + var intervalID = setInterval(() => { + observer.next('hi'); + }, 1000); + + return function unsubscribe() { + clearInterval(intervalID); + }; +} + +var unsubscribe = subscribe({next: (x) => console.log(x)}); + +// Later: +unsubscribe(); // dispose the resources +``` + +The reason why we use Rx types like Observable, Observer, and Subscription is to get safety (such as the Observable Contract) and composability with Operators. diff --git a/docs_app/content/guide/subscription.md b/docs_app/content/guide/subscription.md new file mode 100644 index 0000000000..55616381d5 --- /dev/null +++ b/docs_app/content/guide/subscription.md @@ -0,0 +1,46 @@ +# Subscription + +**What is a Subscription?** A Subscription is an object that represents a disposable resource, usually the execution of an Observable. A Subscription has one important method, `unsubscribe`, that takes no argument and just disposes the resource held by the subscription. In previous versions of RxJS, Subscription was called "Disposable". + +```ts +import { interval } from 'rxjs'; + +const observable = interval(1000); +const subscription = observable.subscribe(x => console.log(x)); +// Later: +// This cancels the ongoing Observable execution which +// was started by calling subscribe with an Observer. +subscription.unsubscribe(); +``` + +A Subscription essentially just has an `unsubscribe()` function to release resources or cancel Observable executions. + +Subscriptions can also be put together, so that a call to an `unsubscribe()` of one Subscription may unsubscribe multiple Subscriptions. You can do this by "adding" one subscription into another: + +```ts +import { interval } from 'rxjs'; + +const observable1 = interval(400); +const observable2 = interval(300); + +const subscription = observable1.subscribe(x => console.log('first: ' + x)); +const childSubscription = observable2.subscribe(x => console.log('second: ' + x)); + +subscription.add(childSubscription); + +setTimeout(() => { + // Unsubscribes BOTH subscription and childSubscription + subscription.unsubscribe(); +}, 1000); +``` + +When executed, we see in the console: +```none +second: 0 +first: 0 +second: 1 +first: 1 +second: 2 +``` + +Subscriptions also have a `remove(otherSubscription)` method, in order to undo the addition of a child Subscription. diff --git a/docs_app/content/navigation.json b/docs_app/content/navigation.json index 63c58fff7a..7151b90e43 100644 --- a/docs_app/content/navigation.json +++ b/docs_app/content/navigation.json @@ -26,7 +26,15 @@ "title": "Overview", "tooltip": "RxJS Overview", "children": [ - { + { + "url": "guide/observable", + "title": "Observables" + }, + { + "url": "guide/subscription", + "title": "Subscription" + }, + { "url": "guide/subject", "title": "Subjects" }