rxjs_如何阅读rxjs大理石图

rxjs

To an experienced RxJS user, marble diagrams are helpful. To someone just starting out with RxJS, they look like a little kid’s drawing in art class.

对于经验丰富的RxJS用户,大理石图会有所帮助。 对于刚开始使用RxJS的人来说,它们看起来像是美术课上的小孩绘画。

I’m by no means an expert user of RxJS, but I’ve used it enough to make sense of these “marble diagrams”. I can finally use rx marbles with confidence, and I want you to get there too.

我绝不是RxJS的专家用户,但是我已经足够使用它来理解这些“大理石图”了。 我终于可以放心使用rx大理石了,我也希望您也能到达那里。

Note: this post assumes you are comfortable with the concept of asynchronous programming and Observables.

注意:本文假定您对异步编程和Observables的概念感到满意。

让我们开始简单 (Let’s start simple)

Image for post

Observables are asynchronous operations, so we need a way to represent the passage of time. This can be done with an arrow moving left to right.

可观察值是异步操作,因此我们需要一种表示时间流逝的方法。 这可以通过左右移动箭头来完成。

Image for post

The vertical line at the end of an arrow represents the successful completion of an Observable. But what if the Observable has an error?

箭头末端的垂直线表示Observable的成功完成。 但是,如果Observable有错误怎么办?

Image for post

If an error occurs in an Observable, it is represented by an X. Once an error is emitted, the Observable does not emit any further values.

如果Observable中发生错误,则用X表示。一旦发出错误,Observable将不再发出任何其他值。

Image for post

And finally, those colorful little circles represent values and can show up anywhere on the arrow’s timeline. These values can be strings, numbers, booleans, or any other basic type.

最后,这些彩色的小圆圈代表值,并且可以显示在箭头的时间轴上的任何位置。 这些值可以是字符串,数字,布尔值或任何其他基本类型。

拼凑在一起 (Putting the pieces together)

Remember, marble diagrams help us understand operators. And operators come in two forms:

请记住,大理石图可帮助我们了解操作员。 运算符有两种形式:

  1. Creation operators (of, from, timer, etc.)

    创建运算符(来自,来自计时器等)
  2. Pipeable operators (map, take, filter, etc.)

    管道运算符(地图,获取,过滤器等)

Creation operators are standalone (they create their own values), which means their marble diagrams are just a single arrow:

创建运算符是独立的(它们创建自己的值),这意味着它们的大理石图只是一个箭头:

Image for post
rx marbles) rx大理石)

And pipeable operators need an “Input Observable” as their source because they do not emit values themselves. They simply “operate on” those values. Therefore, you’ll see pipeable operator marble diagrams with 1 or more “Input Observables”, the operator itself, and an “Output Observable”.

可管道运算符需要“ Input Observable”作为其源,因为它们本身不会发出值。 他们只是“操作”这些价值观。 因此,您将看到带有1个或多个“ Input Observables”,运算符本身和“ Output Observable”的可管道运算符大理石图。

Just think of these like normal functions (technically “pure functions”) except their arguments are observables and their return values are observables.

只需将它们视为普通函数(技术上为“纯函数”),只是它们的参数是可观察的,而其返回值是可观察的。

Here’s an example:

这是一个例子:

Image for post
concat operator
concat运算符

It is important to note that the order of the input Observables matters in some cases. While some operators would return the same output Observable regardless of the order of the two input Observables, some operators actually use the order of those inputs to form the output. The above Observable concat()is a perfect example of this. Notice how the output Observable returns the three values emitted from input Observable #1 before returning the two values emitted from input Observable #2 even though both of Observable #2’s values were emitted prior to the final two values of Observable #1.

重要的是要注意,输入Observable的顺序在某些情况下很重要。 尽管某些运算符将返回相同的输出Observable而不管两个输入Observable的顺序如何,但某些运算符实际上使用这些输入的顺序来形成输出。 上面的Observable concat()是一个完美的例子。 请注意,即使两个Observable#2的值都在Observable#1的最后两个值之前发出,输出Observable还是在返回从输入Observable#2发出的两个值之前如何返回输入Observable#1发出的三个值

In RxJS, we generally refer to input Observable #1 as the “Outer Observable” and input Observable #2 as the “Inner Observable”.

在RxJS中,我们通常将输入Observable#1称为“外部可观测”,将输入Observable#2称为“内部可观测”。

As I said, the order doesn’t always matter. Take the merge() operator for example:

如我所说,顺序并不总是很重要。 以merge()运算符为例:

Image for post
rx marbles) rx大理石)

No matter what order the two input Observables are called, the output Observable will always emit the same values (trust me for now — we’ll learn to read these in a few minutes).

无论调用两个输入Observable的顺序如何,输出Observable都将始终发出相同的值(现在请相信我-我们将在几分钟内学会读取它们)。

暂停:让我们确保在几件事上保持清晰 (Pause: Let’s make sure we’re clear on a few things)

To understand this post going forward, you need to get clear with some terminology:

为了理解本文的发展,您需要弄清楚一些术语:

Outer Observable: The outer Observable, or what I have called “input Observable #1”, or “OO”, is the Observable that is at the top of each diagram. It is called “outer” because it usually appears that way when writing code:

外部可观察到的:外部可观察到的,即我所谓的“输入可观察的#1”或“ OO”,是每个图顶部的可观察到的。 之所以称为“外部”,是因为在编写代码时通常以这种方式出现:

// An example that shouldn't make sense yet to you
outerObservable().pipe(
mergeMapTo(
innerObservable(),
(x, y) => x + y
)
);

Inner Observable: The inner Observable, or what I have called “input Observable #2”, or “IO”, is the Observable below the outer Observable, but before the operator in each diagram. It is called “inner” for the same reason as above.

内部可观测值:内部可观测值,或我所谓的“输入可观测值2”或“ IO”,是外部可观测值下方但在每个图中的运算符之前的可观测值。 出于与上述相同的原因,它被称为“内部”。

Output Observable: When using RxJS operators, sometimes there are many layers between the input Observable(s) and the output Observables, but you can think of the output Observable as the “return value”.

输出可观测值:使用RxJS运算符时,有时输入可观测值和输出可观测值之间有很多层,但是您可以将输出可观测值视为“返回值”。

input Observable: This is a general term to identify any Observable that is NOT the “output Observable”. In other words, both the inner and outer Observables are considered “input” Observables.

输入可观察值:这是一个通用术语,用于标识不是“输出可观察值”的任何可观察值。 换句话说,内部和外部可观察对象都被视为“输入”可观察对象。

And lastly, not all operators follow the concept of “inner” and “outer” Observables. To some operators like combineLatest (we’ll see this later), all Observables are treated equally, and therefore, we refer to each Observable as an “input Observable”.

最后,并非所有运营商都遵循“内部”和“外部”可观测对象的概念。 对于诸如combineLatest运算符(我们将在后面看到),所有Observable均被平等对待,因此,我们将每个Observable称为“输入Observable”。

让我们开始翻译其中一些 (Let’s begin translating a few of these)

Below are some of the most common operators and how to translate their marble diagrams.

以下是一些最常见的运算符以及如何转换其大理石图。

We’ll start easy with the map() operator.

我们将从map()运算符开始。

Image for post
rx marbles) rx大理石)

The top arrow represents our input Observable, and it emits three values. This one is pretty straightforward if you’ve worked with the map function on a basic JavaScript array. All you’re doing is transforming the values emitted from the input Observable by a factor of 10. Here is the marble diagram recreated in code:

上方的箭头代表我们的输入Observable,它发出三个值。 如果您已经在基本JavaScript数组上使用map函数,那么这非常简单。 您要做的只是将输入Observable发出的值转换10倍。这是在代码中重新创建的大理石图:

const { interval } = Rx;
const { map, take } = RxOperators;


// The input observable will emit the values 1, 2, 3 
// in 1 second intervals
function inputObservable() {
  const returnValues = [1, 2, 3];
  return interval(1000)
    .pipe(
    	take(returnValues.length),
    	map(val => returnValues[val])
  	);
}


// =====================================
// Don't worry about anything above this
//
// Just know that the input Observable
// returns the values 1, 2, 3
// =====================================


inputObservable().pipe(
  
  // 10 * 1 = 10
  // 10 * 2 = 20
  // 10 * 3 = 30
  map(x => 10 * x)
)
Image for post
Visualization of the code above
可视化上面的代码

We’ll do one more simple one and then jump into some harder ones. Below is the take() operator.

我们将再做一个简单的例子,然后跳入更难的例子。 下面是take()运算符。

Image for post

In the above diagram, the input Observable emits four integers — 1, 2, 3, and 4. If you were to subscribe to this input Observable directly, you would receive those exact four values. But if you pipe the take(2) operator, the new output Observable will grab the first two emitted values, and then it will complete. The input Observable will still emit the last two values, but our output Observable will not see them because it has completed after two values. Below is the code and visualization.

在上图中,输入Observable发出四个整数-1、2、3和4。如果您直接预订此输入Observable,则将接收到这四个值。 但是,如果通过管道传递take(2)运算符,则新的输出Observable将获取发出的前两个值,然后它将完成。 输入Observable仍将发出最后两个值,但是我们的输出Observable将看不到它们,因为它在两个值之后已完成。 下面是代码和可视化。

const { interval } = Rx;
const { map, take } = RxOperators;


// The input observable will emit the values 1, 2, 3 
// in 1 second intervals
function inputObservable() {
  const returnValues = [1, 2, 3];
  return interval(1000)
    .pipe(
    	take(returnValues.length),
    	map(val => returnValues[val])
  	);
}


// =====================================
// Don't worry about anything above this
//
// Just know that the input Observable
// returns the values 1, 2, 3
// =====================================


inputObservable().pipe(
  take(2)
)
Image for post
the output Observable
输出可观察

switchMap运算符(switchMap Operator)

Let’s look at some more difficult operators now. We’ll start with switchMap() as this is a common, yet sometimes difficult to understand operator.

现在让我们来看一些更困难的运算符。 我们将从switchMap()开始,因为这是一个常见的运算符,但有时很难理解。

Image for post

And this is where these diagrams start getting a bit confusing, but by walking through each emitted value, we can start to make sense of it.

这就是这些图开始变得有些混乱的地方,但是通过遍历每个发出的值,我们可以开始理解它。

But before we go into that, let me highlight a few things.

但是在开始之前,我先强调一些事情。

  • Notice that in this marble diagram, there are two input Observables, which means the code that you will see below this explanation is a bit more confusing. With an operator like switchMap(), Input Observable #1 is called the “Outer Observable” and Input Observable #2 is called the “Inner Observable”

    请注意,在此大理石图中,有两个输入Observables,这意味着您将在此解释下方看到的代码更加混乱。 使用类似switchMap()的运算符,输入可观察值1称为“外部可观察”,输入可观察值2称为“内部可观察”

  • This example is from the official RxJS documentation because the rx marbles example is outdated (as of the time of writing) and uses the deprecated resultSelector.

    本示例来自RxJS官方文档,因为rx marbles示例已过期(截至撰写本文时),并且使用了不赞成使用的resultSelector

  • Because this is an example from the official documentation, you will notice that in the operator, there are a few dashes “-” within the function. You’ll also notice the “|” at the end. These simply represent the passage of time just as we have seen in the actual arrows. The more dashes, the more time between the emitted values. As you can see, the first two values of the outer observable take longer to emit than the last and have more dashes.

    因为这是官方文档中的示例,所以您会注意到,在运算符中,函数中有一些破折号“-”。 您还会注意到“ |” 在最后。 正如我们在实际箭头中所看到的,它们只是代表时间的流逝。 破折号越多,发射值之间的时间越长。 如您所见,外部observable的前两个值发射的时间比最后一个长,并且具有更多的破折号。

Here’s the diagram again so you don’t have to keep scrolling up.

再次显示该图,因此您不必继续向上滚动。

Image for post

Let’s assume that each orange vertical line at the top represents 1 second. This means that the outer Observable (OO) emits values at 1, 4, and 5.5 seconds while the inner Observable (IO) emits values every second for 3 seconds, starting immediately after subscription (time zero).

假设顶部的每条橙色垂直线代表1秒。 这意味着外部Observable(OO)在1、4和5.5秒时发出值,而内部Observable(IO)在订阅后立即开始(时间为零),每3秒每秒发出一次值。

The first three output values (10, 10, 10) seem pretty easy. Just multiply 1 x 10 = 10 for each of them according to the operator logic. Based on these first three values, we could say that for each OO value, the IO emits all of its values.

前三个输出值(10、10、10)似乎很简单。 只需根据操作员逻辑将每个乘以1 x 10 = 10。 基于这前三个值,我们可以说对于每个OO值,IO都会发出其所有值。

This assumption seems to hold true for the first output value of 30… And the second output value of 30…

对于第一个输出值30…和第二个输出值30…,此假设似乎成立。

But shouldn’t it emit a final value of 30??

但是它不应该发出30的最终值吗?

This is where the switchMap logic comes in. Every time the OO emits a value, the IO emits all of its values unless the OO emits a new value before the IO finishes emitting all of its values. We can see this when the OO emits a value of 5 and it appears that our last value of 30 is “canceled”.

这就是switchMap逻辑的所在。每次OO发出一个值时,IO都会发出其所有值,除非OO在IO完成发出其所有值之前先发出一个新值。 当OO发出的值为5且我们的最后一个值为30时,我们可以看到这一点。

Takeway: What the marble diagram above is attempting to convey is that the output Observable of a switchMap operator is dictated by the outer Observable’s emitted values. How would you know this by looking at the diagram? In this case, you probably wouldn’t. Sometimes marble diagrams are only meaningful in conjunction with the operator description within the documentation. Here’s what the official documentation states:

总结:上面的大理石图试图传达的是, switchMap运算符的输出Observable由外部Observable的发射值决定。 通过查看图表,您如何知道这一点? 在这种情况下,您可能不会。 有时,大理石图仅与文档中的操作员说明结合使用才有意义。 官方文件说明以下内容:

Projects each source value to an Observable which is merged in the output Observable, emitting values only from the most recently projected Observable.

将每个源值投影到一个Observable,将其合并到输出Observable中,仅从最近投影的Observable发出值。

Translated, the “source value” would be the outer Observable values, while the “most recently projected Observable” represents the inner Observable values.

换算后,“源值”将是外部可观察值,而“最近投影的可观察”则是内部可观察值。

To some, this may not be confusing. But to me, it is. That’s why I generally start with the marble diagram, use some deductive reasoning, and then confirm my understanding with the official documentation (or other online tutorials).

对于某些人来说,这可能不会造成混淆。 但是对我来说是。 这就是为什么我通常从大理石图开始,使用一些演绎推理,然后通过官方文档(或其他在线教程)确认我的理解。

Another takeaway: There is no “formula” for interpreting marble diagrams. As you’ll see in subsequent examples, interpreting marble diagrams is kind of like solving a logic puzzle. By figuring out what the operator does not do, you will be able to figure out what it does (for you Taleb readers — “via negativa”).

另一个要点:没有用于解释大理石图的“公式”。 正如您将在后续示例中看到的那样,解释大理石图有点像解决逻辑难题。 通过弄清楚操作员不做什么,您将能够弄清楚它的工作(对于您的Taleb读者-“通过negativa”)。

Below is some code that attempts to replicate the diagram above (note that there are more efficient ways to write the inner and outer observables, but this was the clearest way I could think of).

下面的代码尝试复制上面的图(请注意,有更有效的方式编写内部和外部可观察对象,但这是我能想到的最清晰的方法)。

const { interval, Observable } = Rx;
const { map, take, switchMap, startWith } = RxOperators;


// Emit value 1 at 1 second
// Emit value 3 at 4 seconds
// Emit value 5 at 5.5 seconds
// Complete at 5.5 seconds
function outerObservable() {
  return new Observable(subscriber => {
    setTimeout(() => {
    	subscriber.next(1);
    }, 1000);
    
    setTimeout(() => {
    	subscriber.next(3);
    }, 4000);
    
    setTimeout(() => {
    	subscriber.next(5);
      subscriber.complete();
    }, 5500);
  }) 
}


// Emit the value 10 every second, starting at time 0, for 3 total seconds
function innerObservable() {
  const valuesToEmit = [10, 10];
  return interval(1000).pipe(
    take(valuesToEmit.length),
    map(index => valuesToEmit[index]),
    startWith(10) // emits 10 at time zero
  )
}


outerObservable().pipe(
  switchMap(outerObservableValue => {
    return innerObservable().pipe(
      map(innerObservableValue => {
        return innerObservableValue * outerObservableValue;
      })
    );
  })
);
Image for post
Visualization of the code above
可视化上面的代码

另一个示例:CombineLatest(Another example: combineLatest)

Now that we have a basic idea of how to interpret a marble diagram, let’s practice another one — combineLatest.

现在,我们对如何解释大理石图有了基本的了解,让我们练习另一个方法-CombineLatest。

Image for post

Note: combineLatest doesn’t follow the concept of “outer Observable” and “inner Observable”. With this operator, all Observables are treated equally.

注意: combineLatest不遵循“外部可观察”和“内部可观察”的概念。 使用此运算符,所有可观察对象均被平等对待。

This is a good one because you can infer how it works without any descriptions. Starting with the first emitted value, you’ll notice that there is no output value.

这是一个很好的选择,因为您无需任何说明即可推断其工作方式。 从第一个发出的值开始,您会注意到没有输出值。

This tells us that combineLatest requires both input Observables to emit at least one value before it emits the first output value.

这告诉我们, combineLatest要求两个输入Observable都必须发出至少一个值才能发出第一个输出值。

The next emission comes from the second input Observable, and you’ll see that when it emits, we get our first output value, “1A”.

下一个发射来自第二个输入Observable,您将看到当它发射时,我们得到第一个输出值“ 1A”。

Clearly, this operator is combining the values from each input Observable (hence the name). But at this point, we don’t exactly know how yet.

显然,此运算符正在组合来自每个输入Observable的值(因此具有名称)。 但是目前,我们还不完全知道。

Next up, the first input Observable emits the value 2, and we see that our output value is “2A”. It is obvious where the 2 came from, but where did the “A” come from? Well, consistent with its name, “A” was the latest value of the second input Observable.

接下来,第一个输入Observable发出值2,我们看到我们的输出值为“ 2A”。 很明显2是从哪里来的,但是“ A”是从哪里来的呢? 好吧,与其名称一致,“ A”是第二个输入Observable的最新值。

Jumping to the last output value, “5D”, you can see that input Observable #1 emitted the value 5, and the latest emitted value from input Observable #2 was “D”.

跳到最后一个输出值“ 5D”,您可以看到输入Observable#1发出了值5,而来自输入Observable#2的最新发出的值是“ D”。

让我们使用“过滤器”运算符保持滚动 (Let’s keep it rolling with the “filter” operator)

Image for post

We’re back to having only one input Observable. Starting with the first emitted value of 2, we see that there is no output value. Looking at the filter operator logic, we can see that we are looking for emitted values that are greater than 10. We can logically conclude that since the first emitted value was less than 10, it was ignored and there was no output emission.

我们回到只有一个可观察输入。 从第一个发射值2开始,我们看到没有输出值。 查看过滤器运算符的逻辑,可以看到我们正在寻找大于10的发射值。我们可以从逻辑上得出结论,由于第一个发射值小于10,因此将其忽略并且没有输出发射。

By the second value, our understanding is confirmed. The second emitted value is 30, which is greater than 10, and we see that because of this, the output Observable emits the value.

通过第二个值,我们的理解得到了证实。 第二个发出的值是30,大于10,因此我们看到输出Observable发出了该值。

另一个:拉链 (Another one: zip)

Image for post

Looks a lot like combineLatest right?

看起来很像combineLatest对吗?

The first two output Observable values of “1A” and “2B” seem to suggest that zip works exactly like combineLatest. But once we get to the third output Observable value of “3C”, things don’t make sense anymore…

前两个输出的Observable值“ 1A”和“ 2B”似乎表明zip工作方式完全类似于combineLatest 。 但是,一旦达到“ 3C”的第三个输出可观察值,事情就不再有意义了……

If I were reading this marble diagram having never used zip before, I would test out a few assumptions until my assumption held true for all the output values. My initial assumption might have been that this operator combines the latest values of the input Observables. This is proven false by the output value of “3C” because if it emitted the latest values, this should have been “3D”.

如果我正在阅读从未使用过zip大理石图,则我将测试一些假设,直到我的假设对所有输出值都成立为止。 我最初的假设可能是该运算符结合了输入Observable的最新值。 输出值“ 3C”证明这是错误的,因为如果发出最新值,则该值应该为“ 3D”。

So my new assumption — the zip operator “matches up” values of each input Observable. For example, the fourth value of input Observable #1 should be combined with the fourth value of input Observable #2.

因此,我的新假设zip运算符“匹配”每个输入Observable的值。 例如,输入可观察#1的第四值应与输入可观察#2的第四值进行组合。

The fourth value of each Observable is 4 and “D”. Do we have an output value of “4D”?

每个Observable的第四个值是4和“ D”。 我们的输出值为“ 4D”吗?

Yes we do!

是的,我们愿意!

And the fifth value of each Observable is 5 for input Observable #1 and nothing for input Observable #2.

输入Observable#1的每个Observable的第五个值是5,而输入Observable#2的第五个值则是零。

Since both input Observables don’t have a fifth value, there is nothing to “match up” and therefore no output value.

由于两个输入Observable都没有第五个值,因此没有什么可“匹配”的,因此也没有输出值。

上一个:mergeMapTo(具有挑战性) (Last One: mergeMapTo (challenging))

Image for post

Note: I chose this operator because it looked difficult. I have never actually used this operator in my code and don’t know of a good reason to (comment if you do!)

注意:我选择此运算符是因为它看起来很困难。 我从未在代码中实际使用过该运算符,也不知道有充分的理由(如果您这样做,请评论!)

By scanning the operator logic, we can tell that the two input Observables are being combined into a single string value. So let’s use this to make our initial assumption as to how this operator works.

通过扫描运算符逻辑,我们可以知道两个输入Observables正在组合为单个字符串值。 因此,让我们用它来对该操作符的工作方式进行初步假设。

I see that the first output value combines both Observables. This doesn’t tell me much because there are several other operators that do the same (combineLatest, zip, etc.).

我看到第一个输出值结合了两个Observables。 这不会告诉我太多,因为还有其他几个运算符也可以执行相同的操作(combineLatest,zip等)。

The next output is “C1”. This also doesn’t tell me much because combineLatest does the same thing…

下一个输出是“ C1”。 这也没有告诉我太多,因为combineLatest做同样的事情……

The next output value is “A2”, and this is where we start building our assumption. If this operator were combineLatest, this output value should have been “C2” (the latest two values). If this operator were zip, the value should have been “B3”. So now, we must figure out what logic is happening to produce a value of “A2”.

下一个输出值是“ A2”,这是我们开始建立假设的地方。 如果此运算符为combineLatest ,则此输出值应为“ C2”(最近的两个值)。 如果此运算符为zip ,则值应为“ B3”。 因此,现在,我们必须弄清楚产生“ A2”值的逻辑是什么。

This operator is called mergeMapTo (emphasis on “merge”), so it is probably additive. I also see a lot of output values. So a good assumption is that this operator is coming up with every possible combination of input Observable values. Let’s see if this holds true…

此运算符称为merge MapTo(强调“合并”),因此它可能是加法运算。 我也看到很多输出值。 因此,一个很好的假设是该运算符将得出输入可观察值的所有可能组合。 让我们看看这是否成立……

First, let’s list off all the possible combinations of values from the two input Observables:

首先,让我们列出两个输入Observable的所有可能值组合:

A1, A2, A3, C1, C2, C3, B1, B2, B3

And do all of these values appear as output values? Yep.

并且所有这些值都显示为输出值吗? 是的

So the last part of this equation is to figure out how this is being applied. Because with operators, the order and timing of things matters.

因此,该方程式的最后一部分是弄清楚如何应用它。 因为对于运营商而言,事物的顺序和时机至关重要。

Above, I’ve listed all of the possible values using the two input Observable emissions. But the order which I listed them in is NOT the order which they were emitted in. Here is the order they were emitted:

上面,我使用两个输入的“可观察到的排放量”列出了所有可能的值。 但是我列出它们的顺序不是发出它们的顺序。这是发出它们的顺序:

A1, C1, A2, C2, A3, C3, B1, B2, B3

So the question is… Why did “C1” come before “A2”? And why did “A3” come after “C2”? There is clearly something going on with the order of emissions here.

所以问题是……为什么“ C1”比“ A2”要早? 为什么“ A3”在“ C2”之后? 显然,这里的排放顺序正在发生某些事情。

Here’s the diagram again so you don’t have to keep scrolling up.

再次显示该图,因此您不必继续向上滚动。

Image for post

I’m noticing a couple of things here:

我在这里注意到了几件事:

  • Each time either of the input Observables emit a value, the output Observable may, but doesn’t always emit a value (unlike combineLatest)

    每次输入Observable的每一个发出一个值时,输出Observable可能会但不一定总是发出一个值(与combineLatest不同)

  • The output Observable never emits a value that hasn’t been emitted by the input Observables yet

    输出Observable永远不会发出输入Observables尚未发出的值
  • The inner Observable (IO) is directly related to the output value. In other words, each time it emits a new value (1, 2, 3), the output Observable emits a value with that newly emitted value in it. For example, take a look at the output value “A3”. The outer Observable (OO) just emitted the value “B”, and the IO just emitted the value 3. But in this case, the output value doesn’t have “B” in it at all, which means that the output isn’t concerned with the timing of OO, but it is concerned with the timing of IO.

    内部的Observable(IO)与输出值直接相关。 换句话说,每当它发出一个新值(1、2、3)时,输出Observable就会发出一个其中包含该新发出的值的值。 例如,看一下输出值“ A3”。 外部Observable(OO)刚刚发出值“ B”,而IO刚刚发出值3。但是在这种情况下,输出值中根本没有“ B”,这意味着输出为“ B”。 Ť涉及OO的定时,但它涉及IO的定时。

  • The output values look random at first, but notice how “A1”, “A2”, and “A3” are spaced out evenly — the exact same spacing as the IO. This also occurs for “B1”, “B2”, and “B3”, and “C1”, “C2”, “C3”— they are spaced out evenly.

    首先,输出值看起来是随机的,但请注意“ A1”,“ A2”和“ A3”是如何均匀隔开的-与IO完全相同的间距。 对于“ B1”,“ B2”和“ B3”以及“ C1”,“ C2”,“ C3”,也会发生这种情况-它们被均匀地隔开。

So with these observations, here is my conclusion as to how this operator logic works:

因此,根据这些观察,我得出了关于此运算符逻辑如何工作的结论:

Each time the outer Observable (input Observable #1) emits a value, a new “sequence” (not a new Observable) is fired. This sequence is entirely based on the number of values and timing of values seen in the inner Observable (input Observable #2).

每次外部Observable(输入Observable#1)发出一个值时,都会触发一个新的“序列”(而不是一个新的Observable)。 此序列完全基于内部Observable(输入Observable#2)中看到的值的数量和值的时序。

Here’s some code to represent the diagram for those interested:

这是一些代码,用于为感兴趣的人表示该图:

const { interval, Observable } = Rx;
const { map, take, switchMap, startWith, mergeMapTo } = RxOperators;


function outerObservable() {
  return new Observable(subscriber => {
    setTimeout(() => {
    	subscriber.next("A");
    }, 1000);
    
    setTimeout(() => {
    	subscriber.next("C");
    }, 1500);
    
    setTimeout(() => {
    	subscriber.next("B");
      subscriber.complete();
    }, 4000);
  }) 
}


// Emit the value 10 every second
function innerObservable() {
  return interval(1000).pipe(map(val => val + 1),take(3));
}


outerObservable().pipe(
  mergeMapTo(
    innerObservable(),
    (x, y) => x + y
  )
);
Image for post
Visualization of code above
上面代码的可视化

有多种方法可以做到这一点(There is more than one way to do it)

Reading marble diagrams is more of an art than a science. Once you understand the components of a marble diagram, you can use deductive logic (as seen above), you could search the internet for tutorials on the specific operator you are using (probably quicker than deductive logic), or you could painfully learn to read the RxJS documentation and all of the jargon associated with that. Or… You could combine all of these approaches into one (my preference).

阅读大理石图更多的是一门艺术,而不是一门科学。 一旦理解了大理石图的组成部分,就可以使用演绎逻辑(如上所示),可以在互联网上搜索正在使用的特定运算符的教程(可能比演绎逻辑更快),或者可以痛苦地学习阅读RxJS文档以及与此相关的所有行话。 或者……您可以将所有这些方法组合为一种(我的偏爱)。

And as you can see, the more operators that you learn, the easier it is to learn new ones because you can start making associations between them and recognizing operators that are similar to one another.

正如您所看到的,您学习的运算符越多,学习新的运算符就越容易,因为您可以开始在它们之间建立关联并识别彼此相似的运算符。

运营商决策树 (The Operator Decision Tree)

And finally… Just because you can read marble diagrams does not mean that you need to learn every operator. While learning all the operators thoroughly would be extremely helpful, not all of us have the time to do so (and after using RxJS long enough, you’ll find yourself re-using the same few operators over and over again). And for that reason, here’s a workflow that I’ve used with RxJS:

最后……仅仅因为您可以阅读大理石图并不意味着您需要学习每个操作员。 尽管全面了解所有运算符将非常有帮助,但并非所有人都有时间这样做(并且在使用RxJS足够长时间之后,您会发现自己一次又一次地重复使用相同的少数运算符)。 因此,这是我与RxJS一起使用的工作流程:

  1. Identify the problem you’re trying to address (aka you know you need an operator but aren’t sure which one)

    确定您要解决的问题(也就是知道您需要操作员,但不确定是哪一个)
  2. Use the operator decision tree

    使用操作员决策树

  3. Interpret the marble diagram of the operator that the decision tree chooses

    解释决策树选择的操作员的大理石图
  4. If your interpretation of the operator seems like it addresses the problem from step #1, you’re done. If not, repeat these steps until you’ve narrowed it down enough.

    如果您对运算符的解释似乎解决了步骤1中的问题,则说明您已完成。 如果没有,请重复这些步骤,直到将其缩小到足够的范围为止。

而已! (That’s it!)

Here are some other articles of mine you might like:

这是您可能会喜欢的其他一些文章:

Three things I wish I knew when I started using Angular 2+

当我开始使用Angular 2+时我希望知道的三件事

The most confusing thing in JavaScript: The this keyword

JavaScript中最令人困惑的事情: this关键字

翻译自: https://medium.com/@zach.gollwitzer/how-to-read-an-rxjs-marble-diagram-f6e8dfa29781

rxjs

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值