Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(tutorial): Adding a comprehensive tutorial on RxJS, done as I usually teach it. #5592

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

benlesh
Copy link
Member

@benlesh benlesh commented Jul 16, 2020

Starting to add a series of documents outlining the basics of how to use RxJS. Still a lot more to go.

This, effectively, is how I teach RxJS to people.

This adds:

  • Subscription
  • Unsubscription

Also:

  • Additional detail provided in UnsubscriptionError docs.

NOTES:

I still need to go back through and add appropriate URLs to the links that are in the markdown files. (GL) means it is a glossary link, and (API) means it is a link to an API resource doc.

Description:

Related issue (if exists):

@benlesh benlesh marked this pull request as draft July 16, 2020 00:03
@benlesh benlesh changed the title docs(tutorial): subscribe and unsubscribe docs(tutorial): Adding a comprehensive tutorial on RxJS, done as I usually teach it. Jul 16, 2020
@niklas-wortmann
Copy link
Member

I was planning a slightly different strategy for a tutorial section in the docs. IMO a tutorial should be very task-driven, like the tour of heroes where you end up with an application without knowing anything about RxJS. This tutorial might be a little bit intimidating for newcomers as they probably have to look up several words and it also requires some understanding of javascript in general. The content of this pr is absolutely amazing, but IMO this should rather go into https://rxjs.dev/guide/subscription.
My intention was to work out a practical example as a tutorial for people to go through that doesn't need any background knowledge. The guide section was intended for quality content that is not directly related to API stuff but more on a meta-layer.
I am super happy to discuss this idea with everyone to reach a consensus on this one.

Btw. in #5419 I initiated a restructuring of the guide section by doing a split in "beginners" and "advanced" content. IMO it would be good to apply this style on this document too

@benlesh
Copy link
Member Author

benlesh commented Jul 16, 2020

@niklas-wortmann We don't need to call this a "tutorial". I just couldn't think of a better name right off.

This is more of a brain dump of everything and anything I know about RxJS (within reason) in the order I'd tell it.

@benlesh
Copy link
Member Author

benlesh commented Jul 16, 2020

Also, @niklas-wortmann, I'm very sorry I didn't see your other PR. This is just something I've wanted to do for ages. I've finally gotten around to doing it. I appreciate any feedback you can give, but it's really important to me that this lands. (It goes along with the glossary work, etc)

@niklas-wortmann
Copy link
Member

I discussed with @benlesh offline that this pr might replace the whole guide section. This is the current draft I will think about the proper place to present this in the docs.


- Calls to [`subscribe`](API) return a [`Subscription`](API) object.
- Calling [`unsubscribe`](API) on the [`Subscription`](API) object notifies the [producer](GL) to stop sending values, and triggers the [teardown](GL) of any underlying resources the [producer](GL) has registered for teardown.
- Failure to [unsubscribe](GL) from asynchronous observables will result in unnecessary resource use, and even memory leaks.
Copy link

Choose a reason for hiding this comment

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

Shouldn't that be balanced by saying that a completion or an error automatically unsubscribes? In my experience with Angular, most observables (HTTP, Form, ActivatedRoute observables) are short-lived and guaranteed to complete or error.

const firehose$ = new Observable(subscriber => {
let n = 0;
while (true) {
subscriber.notify(n++);
Copy link

Choose a reason for hiding this comment

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

Shouldn't that be subscriber.next(n++)?

// This check will prevent this loop from running forever
// as long as there is a `take`, etc.
while (!subscriber.closed) {
subscriber.notify(n++);
Copy link

Choose a reason for hiding this comment

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

Shouldn't that be subscriber.next(n++)?

Synchronously, when a subscriber has called `complete` or `error`, or when a consumer has [unsubscribed](GL), there is a flag property on [`Subscriber`](API) called [`closed`](API) that will be flipped to `true`. This is useful for checking whether to continue things like synchronous loops. (NOTE: That you do _NOT_ have to check `closed` every time before calling `next`, `error`, or `complete`, RxJS does an internal check there for you).

```ts
// BAD: this is going to lock your thread on subscribe!
Copy link

@jnizet jnizet Jul 18, 2020

Choose a reason for hiding this comment

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

I'm confused. This seems like the way to fix the above bad example, but it's still commented as BAD. Also, the previous paragraph says that you do not have to check closed before calling next, but this example precisely does that.


## Overview

Chances are, the first time you encounter an [Observable](API), it is because you have had one returned to you by some other API. In this case, the most helpful thing for you to know is how to get values from the observable, and the semantics of [subscription](GL)

Choose a reason for hiding this comment

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

Missing period.


> The first thing to know is, an observable doesn't "do" anything.

An observable, in fact, is _not_ a "stream". An observable is a template for [subscription](GL), through which your code (the [consumer](GL)), will tell a [producer](GL) to start pushing values to it by subscribing to an observable, creating a [subscription](GL).
Copy link

@DeborahK DeborahK Jul 19, 2020

Choose a reason for hiding this comment

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

Consider making these two sentences: "An observable is a template for a subscription. Subscribing to the observable creates the subscription, through which your code (the consumer), will tell a producer to start pushing values to it." This makes the text a little easier to digest.

});
```

The most basic form of subscription is to subscribe to the observable with a single function as the argument to [`subscribe`](API). This will execute the observable's initialization function and create the [subscription](GL).

Choose a reason for hiding this comment

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

"the observable's initialization function" has not been discussed previously. Should that be covered before here? Described in more detail here? Or removed from here to stick with a more simplistic discussion of this at this point and discussed in detail later?

})
```

An `Observer` is a type, basically any object with a `next`, `error`, and `complete` method on it. A "partial observer" is any object with at least one of those methods. It is a way to pass these three handlers to the subscription so that a [producer](GL) can notify your code (the [consumer](GL)). `next` is called when the producer pushes a value to the consumer. `error` is called when the producer has encountered an error and must stop sending values, and `complete` is called when the producer has pushed all values to the consumer, and will push no more.
Copy link

@DeborahK DeborahK Jul 19, 2020

Choose a reason for hiding this comment

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

Wording: Sometimes words like "it" and "this" can be difficult to match back up to what it's referencing, especially when covering complex topics. Consider instead of "It is a way..." to go with "The Observer object provides a way ..." or something similar.

All unhandled errors will be rethrown in a different call context. This is done for a variety of reasons, but what is important to know is: Even if your observable is totally synchronous, an error from your observable _cannot be caught_ by wrapping the [`subscribe`](API) call in a `try-catch` block.


### Handled Errors

Choose a reason for hiding this comment

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

Should this be ## instead of ### so it's at the same level as "Unhandled Errors"?

})
```

An `Observer` is a type, basically any object with a `next`, `error`, and `complete` method on it. A "partial observer" is any object with at least one of those methods. It is a way to pass these three handlers to the subscription so that a [producer](GL) can notify your code (the [consumer](GL)). `next` is called when the producer pushes a value to the consumer. `error` is called when the producer has encountered an error and must stop sending values, and `complete` is called when the producer has pushed all values to the consumer, and will push no more.
Copy link

@DeborahK DeborahK Jul 19, 2020

Choose a reason for hiding this comment

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

Concepts: The term "callback" is used in the Handled Errors section. Consider introducing the term here. Something like: "The next, error, and complete methods are often called callback functions because they are "called back" when an action occurs." inserted before "next is called when ..."

- Calls to [`subscribe`](API) return a [`Subscription`](API) object.
- Calling [`unsubscribe`](API) on the [`Subscription`](API) object notifies the [producer](GL) to stop sending values, and triggers the [teardown](GL) of any underlying resources the [producer](GL) has registered for teardown.
- Failure to [unsubscribe](GL) from asynchronous observables will result in unnecessary resource use, and even memory leaks.
- Any errors that occur during teardown will be collected and rethrown as an [`UnsubscriptoinError`](API).

Choose a reason for hiding this comment

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

Misspelling: UnsubscriptoinError


## Overview

Just as you need to know how to start getting values from your observable, you must also need to know how to tell the [producer](GL) to stop sending values, and perhaps more importantly, to [teardown](GL) and free up resources.

Choose a reason for hiding this comment

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

Minor: Consider changing this: "you must also need to know" to this "you must also know".


## Subscriptions

All calls to [`subscribe`](API) return a [`Subscription`](API) object. In order to end your [subscription](GL) and tell the [producer](GL) to stop sending values and [teardown](API), you must call [`unsubscribe`](API).

Choose a reason for hiding this comment

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

Minor: Consider changing "In order to end..." to "To end ..."


All calls to [`subscribe`](API) return a [`Subscription`](API) object. In order to end your [subscription](GL) and tell the [producer](GL) to stop sending values and [teardown](API), you must call [`unsubscribe`](API).

When [`unsubscribe`](API) is called, it will synchronously trigger the [teardown](GL) of the entire [stream](GL) and all underlying [producers](GL) from [cold](GL) sources. This means from that exact moment on, no values can be [nexted](GL), and you cannot be [notified](GL) of any errors or completions.

Choose a reason for hiding this comment

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

Haven't previously introduced the concept of "cold" sources. Is there another way to say that so that concept doesn't need to be introduced here?


When [`unsubscribe`](API) is called, it will synchronously trigger the [teardown](GL) of the entire [stream](GL) and all underlying [producers](GL) from [cold](GL) sources. This means from that exact moment on, no values can be [nexted](GL), and you cannot be [notified](GL) of any errors or completions.

By the time the [`unsubscribe`](API) call returns, an attempt to [teardown](GL) has completed.

Choose a reason for hiding this comment

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

I'm unclear on this wording: "an attempt to teardown has completed". Should this be "any attempt ..." Or just "all teardown is complete"?

console.error(error);
}
}
```
Copy link

@DeborahK DeborahK Jul 19, 2020

Choose a reason for hiding this comment

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

Consider adding a section on "When Unsubscribe is Not Needed" (or similar) that details the additional items suggested for the TLDR: That an error automatically unsubscribes and that any Observable that completes will automatically be unsubscribed. And consider including examples.


## TLDR:

- You should probably use a provided [creation function](API).

Choose a reason for hiding this comment

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

These TLDR sections are great!

Consider re-wording the first two items to be more clear on the recommendation:

  • "Use a provided creation function to create an Observable"
  • "Only create an Observable with the new Observable constructor if no creation function will suffice"

- The constructor wraps an initialization function that will be executed synchronously every time you call [`subscribe`](API).
- The initialization function gives you a [`Subscriber`](API) instance to push [notifications](GL) to [consumers](GL).
- The initialization function is expected to return [teardown](GL) logic, which is either a `() => void`, [`Subscription`](API), or `void` (if there is no teardown to be done).
- When `complete` or `error` are called on the [`Subscriber`] provided by the initialization function, it will execute the provided [teardown](GL) as soon as possible.

Choose a reason for hiding this comment

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

Should this: [Subscriber] be this: Subscriber in the last bullet?


## Overview

Besides consuming observables, another thing you'll likely need to do is create new observables. Creating an observable is relatively straight forward, as it is effectively wrapping a function to be executed later with some guarantees. But more importantly, RxJS provides a host of observable [creation functions](API) that create observables for you in tested ways.

Choose a reason for hiding this comment

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

Consider elaborating on this point: "as it is effectively wrapping a function to be executed later with some guarantees" What does that mean, exactly? When later is it executed (on creation I presume)? And what types of guarantees?

## Creation Functions

A creation function, in RxJS terms, is simply a standalone function provided by RxJS that takes some arguments and returns an [`Observable`](API). There are many examples of these creation functions, but the most common are:

Copy link

@DeborahK DeborahK Jul 19, 2020

Choose a reason for hiding this comment

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

I assume these will all link to examples, but consider adding at least one example here anyway ... maybe the of (and if space permits one of the more async-ish ones like interval) ... so the reader can get an idea of what these look like without having to link off somewhere else.


It is highly recommended that you try to use these functions to create your observable. They are well tested, and used in a huge number of projects, and can generally be composed to create most observables you need.

## Observable Construction
Copy link

@DeborahK DeborahK Jul 19, 2020

Choose a reason for hiding this comment

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

This section gets quite technical rather quickly and this is still pretty early on in the reader's journey.

Consider leaving this introduction paragraph on Observable Construction, so the reader knows there is an alternative. Maybe mention it gets complex. But then move all of the remaining content to a separate section much later in this document and linking to that here. That prevents a reader newer to these concepts from getting lost and stopping at this point.


## TLDR:

- Observables are a container for a collection of values over time.
Copy link

@DeborahK DeborahK Jul 20, 2020

Choose a reason for hiding this comment

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

In Chapter 1 the text is: "An observable, in fact, is not a "stream". An observable is a template for subscription..." To prevent confusion, is there a way to bring these two definitions together? Maybe add this line also to Chapter 1 under "Overview"? Something like "You can think of Observables as a container for a collection of values over time."

@@ -0,0 +1,108 @@
# ???. But RxJS Is Hard

We understand. Believe me. But maybe you're not seeing the real value of what RxJS offers, which is the [`Observable`](API) type itself. There are many [benefits to using observables](1000-why-observables.md), and you don't have to use [operators](GL) at all to get those benefits.

Choose a reason for hiding this comment

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

Consider leaving off the "but maybe ..." sentence as it may be construed to be condescending.

<ul><li *ngFor="let item of data">{{item}}<li></ul>
`
})
const SomeComponent {

Choose a reason for hiding this comment

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

And even though it's pseudo code, consider going with export class to more closely match the React example.


**Plain DOM Manipulation**

Presented for those who don't quite follow the Angular or React examples above.

Choose a reason for hiding this comment

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

consider changing "who don't quite follow" to "who may not be interested in" to not sound condescending.

If this is the case, I'd recommend using an online playground/sandbox app like [Stackblitz](https://stackblitz.com), or [Codesandbox](https://codesandbox.io), et al, and importing `rxjs` and just playing with it.

### Recommendations for playing with operators:

Choose a reason for hiding this comment

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

Links to a few sample stackblitz examples could be useful here, giving readers a starting point.

2. [Unsubscribing](2-unsubscribing.md)
3. [Creating An Observable](3-creating-an-observable.md)
4. [What Is An Operator?](4-what-is-an-operator.md)
5. [Implementing Operators](5-implementing-operators.md)

Choose a reason for hiding this comment

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

Consider moving this section down as it is a bit more complex and implementing ones own operators is not a common "starter" task.

Copy link

@jnizet jnizet left a comment

Choose a reason for hiding this comment

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

I really liked what I read, as usual. 👍

The next developer will have a hard time **understanding** your code if they lack one or both of these things:

1. Context around why the code is doing what it is doing.
2. Knowledge of what the abstractions your using do.
Copy link

Choose a reason for hiding this comment

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

your -> you're


## Break Your Code Up

You wouldn't write a "mega function" in most cases, don't write "mega observables". Observables are effectively functions. As functions don't do anything until you call the, observables don't do anything until you _subscribe_ to them. When you're building an observable by chaining operators together, what you're doing is effectively writing a function by adding lines of code to it, so to speak. Very long functions can be hard to understand because they can be hard to follow. Variable declarations can slowly move away from where they are used over the evolution of the code, and the local variable context gets cluttered with names that can start to conflict with one another.
Copy link

Choose a reason for hiding this comment

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

you call the -> you call them


You wouldn't write a "mega function" in most cases, don't write "mega observables". Observables are effectively functions. As functions don't do anything until you call the, observables don't do anything until you _subscribe_ to them. When you're building an observable by chaining operators together, what you're doing is effectively writing a function by adding lines of code to it, so to speak. Very long functions can be hard to understand because they can be hard to follow. Variable declarations can slowly move away from where they are used over the evolution of the code, and the local variable context gets cluttered with names that can start to conflict with one another.

The very nature of reactive programming with observables is that each piece is processed as an individual "step". Every operator returns a new observable. This means that you can break single "mega observable" into smaller observables. This can help with unit testing, in particular.
Copy link

Choose a reason for hiding this comment

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

break single -> break a single


## RxJS Names Are Challenging

Normally, with abstractions, ideally, the abstractions have names that clearly convey what they are doing under the hood and possibly even their use case. Unfortunately, RxJS is a little rough around the edges in this area. Many of our names were inherited from a more than 10-year history dating back to a port from DotNet's Rx implementation. While we have been able to try to change and standardize some of the names (`mergeMap` or `exhaustMap`, for example), there are still some names that exist that don't make much sense to outsiders, such as `BehaviorSubject`. The obvious thing to do would seem to be rename these things in RxJS to names that make more sense. However, given the wide use and momentum of the library, these sorts of changes are costly, take a long time to execute, and just don't make sense at that sort of scale.
Copy link

Choose a reason for hiding this comment

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

Normally, with abstractions, ideally, the abstractions have names

This sounds weird to me. I'd rephrase as

Ideally, abstractions have names


## Using Custom Operators To Improve Readability

Since pipeable operators were introduced in RxJS 5.5, we have had the ability to quickly and easily create custom operators using simple functional concepts. One of the key advantages to this the ability to create operators that are really wrapping other operators with names that concisely convey what the code is doing.
Copy link

Choose a reason for hiding this comment

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

One of the key advantages to this is the ability (missing is)


### Quick Note On "Finnish Notation" ($ suffix)

Adding a `$` suffix to variable names pointing to observable instances is something that started long ago with [Andre Staltz](https://twitter.com/andrestaltz), and continues to be widely practiced today. The RxJS team, does not "officially" think this practice is required or even "better" than not doing so. We have found it advantageous to use Finnish Notation in our documentation because it clearly calls out which variables are observables, and let's face it, we have a lot of documentation on this site that involve important variables that are observables.
Copy link

Choose a reason for hiding this comment

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

The RxJS team, does not -> the RxJS team does not


```ts
/**
* This is an operator used on an observable of save button clicks in our checkout component,
Copy link

Choose a reason for hiding this comment

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

I would end the sentence and start a new one after "component".


## Overview

This section is a guide to how to approach RxJS concepts and how to address common issues while learning and using this library. The goal is to go into as much detail as possible or necessary in as many aspects of RxJS as possible while also trying to provide a quick high-level overview of the concepts.
Copy link

Choose a reason for hiding this comment

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

"a guide to how to" sounds weird to me. "a guide on how to" maybe?


### NO NEED to unsubscribe:

- **Subscriptions that should stay active for the life of your server/web document**. If it needs to stay up for as long as the host environment is open, then there is no need to tear it down. **HOWEVER**. If you have a web application that can be mounted and unmounted, you will want to unsubscribe from all subscriptions owned by that web application. Otherwise, when unmounting the app it will leave it subscriptions active and hang onto resources.
Copy link

Choose a reason for hiding this comment

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

leave it subscriptions -> leave its subscriptions


- **Subscriptions that should stay active for the life of your server/web document**. If it needs to stay up for as long as the host environment is open, then there is no need to tear it down. **HOWEVER**. If you have a web application that can be mounted and unmounted, you will want to unsubscribe from all subscriptions owned by that web application. Otherwise, when unmounting the app it will leave it subscriptions active and hang onto resources.
- **Subscriptions that you know will complete that are delivering a value you always want to get**. For example, if you're loading some expensive to calculate data that you know you're going to use eventually, and you don't want to get it twice, you might choose to allow an observable to run to completion, even if the original consuming code no longer cares about the value.
- **Synchronous observables**. By the time you get the `Subscription` back from a synchronous observable, it is already `complete`, and it as already torndown. There is no need to keep the `Subscription` in memory or unsubscribe from it later (It won't hurt much if you do, but it's unnecessary). Examples of these are things like the result of [`of`](API), [`from`](API), [`range`](API), et al.
Copy link

Choose a reason for hiding this comment

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

it as already -> it has already

2. [Unsubscribing](2-unsubscribing.md)
3. [Creating An Observable](3-creating-an-observable.md)
4. [What Is An Operator?](4-what-is-an-operator.md)
5. [Implementing Operators](5-implementing-operators.md)

Choose a reason for hiding this comment

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

Consider adding a section or multiple sections here introducing some of the other operators with their best practices similar to the "Flattening Operations" section.

The next developer will have a hard time **understanding** your code if they lack one or both of these things:

1. Context around why the code is doing what it is doing.
2. Knowledge of what the abstractions your using do.

Choose a reason for hiding this comment

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

your -> you're


You wouldn't write a "mega function" in most cases, don't write "mega observables". Observables are effectively functions. As functions don't do anything until you call the, observables don't do anything until you _subscribe_ to them. When you're building an observable by chaining operators together, what you're doing is effectively writing a function by adding lines of code to it, so to speak. Very long functions can be hard to understand because they can be hard to follow. Variable declarations can slowly move away from where they are used over the evolution of the code, and the local variable context gets cluttered with names that can start to conflict with one another.

The very nature of reactive programming with observables is that each piece is processed as an individual "step". Every operator returns a new observable. This means that you can break single "mega observable" into smaller observables. This can help with unit testing, in particular.

Choose a reason for hiding this comment

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

Consider changing the last sentence to: "This can help with understanding and with unit testing" (Since this section has been stressing understand-ability.)


Normally, with abstractions, ideally, the abstractions have names that clearly convey what they are doing under the hood and possibly even their use case. Unfortunately, RxJS is a little rough around the edges in this area. Many of our names were inherited from a more than 10-year history dating back to a port from DotNet's Rx implementation. While we have been able to try to change and standardize some of the names (`mergeMap` or `exhaustMap`, for example), there are still some names that exist that don't make much sense to outsiders, such as `BehaviorSubject`. The obvious thing to do would seem to be rename these things in RxJS to names that make more sense. However, given the wide use and momentum of the library, these sorts of changes are costly, take a long time to execute, and just don't make sense at that sort of scale.

The point is, to the unindoctrinated, `saveButtonClicks$.pipe(exhaustMap(() => orderService.saveOrder(cart))))` looks like gobbledygook. If they have never seen an `exhaustMap` before, they are likely very confused and already starting to hate RxJS.

Choose a reason for hiding this comment

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

Consider leaving off the "and already starting to hate RxJS".


## Using Custom Operators To Improve Readability

Since pipeable operators were introduced in RxJS 5.5, we have had the ability to quickly and easily create custom operators using simple functional concepts. One of the key advantages to this the ability to create operators that are really wrapping other operators with names that concisely convey what the code is doing.

Choose a reason for hiding this comment

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

this the -> this is the

(orderResponse) => displayReceipt(orderResponse)
);
```

Copy link

@DeborahK DeborahK Aug 12, 2020

Choose a reason for hiding this comment

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

This technique is great to see in the docs!


## Name Things Effectively

Ideally, most of the names of your variables, functions, methods, and custom operators are "self-documenting". There probably isn't such thing as 100% "self-documenting" code, but good names can help give the next developer cues about intent and the inner workings the code in question.

Choose a reason for hiding this comment

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

workings the code -> workings of the code


In our examples, we frequently will give very generic names like `source$` or `stream$`. That's because our examples are often intentionally generic and simplified to show behavior. This is _not_ how you should name things in your application.

When naming variables, custom operators, functions, etc, err on the side of specificity. Give names that provide some insight into the context, use case, or inner workings of the thing. Instead names like `state$`, or `stateStream$`, provide a name that gives more insight into what sort of state, or where it is used. `applicationState$`, `componentLocalState$`, `retrievedRegionOrStateInfo$`. See? All of those could have been named "`stateStream$`" or "`state$`", but in giving them better names, without even having the context of the code around it, we can more easily see what sort of data these variables might contain.

Choose a reason for hiding this comment

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

Instead names -> Instead of names

}
```

What the goal is for the comments above is really just to give the next developer (or your future self) more context as to why you made particular decisions with the code. Some of it may be obvious to you now, or it may become more obvious to you over time, but that doesn't mean you should not create useful comments for people that don't have the same level of understanding of the code base as you. The "What if you get hit by a bus?" factor should always apply.

Choose a reason for hiding this comment

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

Consider changing the first sentence to "The goal for the comments above is ..." to prevent the double "is".

@benlesh
Copy link
Member Author

benlesh commented Sep 2, 2020

Putting this back on the agenda so we can decide a path forward for getting this out.


If you call subscribe with one function ([Basic Subscription](#Basic_Subscription)), or with a partial [observer](#Subscription_With_An_Observer) that does not have an [error](GL) handler, RxJS will treat all errors pushed by the [producer](GL) as "unhandled". This means that the developer did nothing to handle the error.

All unhandled errors will be rethrown in a different call context. This is done for a variety of reasons, but what is important to know is: Even if your observable is totally synchronous, an error from your observable _cannot be caught_ by wrapping the [`subscribe`](API) call in a `try-catch` block.

Choose a reason for hiding this comment

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

Maybe this "All unhandled errors will be accumulated and will be rethrown in a different call context".

It's a question also whether the errors would be accumulated or not?


## Overview

Just as you need to know how to start getting values from your observable, you must also need to know how to tell the [producer](GL) to stop sending values, and perhaps more importantly, to [teardown](GL) and free up resources.

Choose a reason for hiding this comment

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

Suggestions:
Just as you need to know how to start getting .... --> As you learn how to start getting

you must also need to know how to tell .... --> you must also learn how to tell

### NO NEED to unsubscribe:

- **Subscriptions that should stay active for the life of your server/web document**. If it needs to stay up for as long as the host environment is open, then there is no need to tear it down. **HOWEVER**. If you have a web application that can be mounted and unmounted, you will want to unsubscribe from all subscriptions owned by that web application. Otherwise, when unmounting the app it will leave it subscriptions active and hang onto resources.
- **Subscriptions that you know will complete that are delivering a value you always want to get**. For example, if you're loading some expensive to calculate data that you know you're going to use eventually, and you don't want to get it twice, you might choose to allow an observable to run to completion, even if the original consuming code no longer cares about the value.

Choose a reason for hiding this comment

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

if you're loading some expensive ?? to calculate data that ...
It seems something is missing after expensive.

Instead:
If you're loading some expensive resources to calculate data that ...


- **Never-ending subscriptions**. If you do not unsubscribe from these, they will continue forever and consume memory and computing resources. Examples of this could be a web socket stream, or a simple interval. You don't need your app ticking along processing repetitive tasks it no longer cares about and consuming precious time on that single thread.
- **Long-running, expensive subscriptions**. Subscriptions whose actions or side effects are expensive or no longer necessary must be unsubscribed when no longer in use. This, again, is to free up processing capacity and memory. Large streaming results from HTTP or web sockets, even if you've engineered them to complete after some time, must be torn down when you no longer are interested in their results. This is done to prevent memory leaks and free up resources.
- **Subscriptions that register event handlers**. Subscriptions who register objects or functions (particularly functions with closures) with external event emitters and event targets must be torn down. Such things are a common cause of memory leaks, and functions registered and event handlers that close over other variables and objects (such as a component reference via `this`) can cause large things to be retained in memory indefinitely.

Choose a reason for hiding this comment

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

... with external event emitters and event targets must be torn down. -> with external event emitters and event targets must be unsubscribed.

To keep in, consistency with the previous paragraph which uses the unsubscribed term.

### NO NEED to unsubscribe:

- **Subscriptions that should stay active for the life of your server/web document**. If it needs to stay up for as long as the host environment is open, then there is no need to tear it down. **HOWEVER**. If you have a web application that can be mounted and unmounted, you will want to unsubscribe from all subscriptions owned by that web application. Otherwise, when unmounting the app it will leave it subscriptions active and hang onto resources.
- **Subscriptions that you know will complete that are delivering a value you always want to get**. For example, if you're loading some expensive to calculate data that you know you're going to use eventually, and you don't want to get it twice, you might choose to allow an observable to run to completion, even if the original consuming code no longer cares about the value.

Choose a reason for hiding this comment

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

Subscriptions that you know will complete that are delivering a value you always want to get --> Subscriptions which will complete eventually and are delivering a value you always want to get

@benlesh benlesh removed the AGENDA ITEM Flagged for discussion at core team meetings label Apr 20, 2021
@benlesh
Copy link
Member Author

benlesh commented Apr 20, 2021

FYI: This work is not forgotten. It's just been put on the backburner while I try to focus on the v7 release.

@benlesh
Copy link
Member Author

benlesh commented May 24, 2021

I just got back to this. Moved files around and started updating links. I'll go back through the comments here and see how much of this still applies.

@benlesh benlesh mentioned this pull request May 27, 2021
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants