简体   繁体   English

RXJS:为什么 mergemap 在示例中不提供来自多个内部可观察对象的输出?

[英]RXJS: Why is mergemap not providing output from multiple inner observables in example?

I am trying to better understand mergemap by recreating a very simple example and it is not behaving as I expected from my understanding of the official description.我试图通过重新创建一个非常简单的示例来更好地理解mergemap,并且它的行为不像我对官方描述的理解所期望的那样。

In this example each successive document click outputs a single result (ie the output of the "of" observable, which is behaviour I would have expected from switchmap. My understanding of mergemap is that it will merge the results of multiple inner observables which does not appear to be the case here.在这个例子中,每个连续的文档点击都会输出一个结果(即“of”observable 的输出,这是我对 switchmap 的预期行为。我对 mergemap 的理解是它将合并多个内部 observables 的结果,而不是似乎是这里的情况。

To examine the lifecycle of the observable I included the verbose version of the subscribe() function so I could see if complete() is called, but it isn't, so I expected the results of multiple of observerables to accumulate with successive document click and be presented in the output on each click.为了检查 observable 的生命周期,我包含了 subscribe() 函数的详细版本,所以我可以查看是否调用了 complete(),但它不是,所以我希望多个 observables 的结果随着连续的文档点击而累积并在每次点击时显示在输出中。 However what happens is a single new observable result is created each time.然而,每次都会创建一个新的可观察结果。 Why does each successive document click only emit one observable stream, and discard the previous one.为什么每个连续的文档点击只发出一个可观察的流,并丢弃前一个。 Isnt this the behaviour of switchmap not mergemap?这不是switchmap的行为而不是mergemap吗?

fromEvent(document, 'click')
  .pipe(mergeMap(() => of(Date.now())))
  .subscribe({
    next(x) {
      console.log('value ', x);
    },
    complete() {
      console.log('done');
    },
  });

In this example each successive document click outputs a single result (ie the output of the "of" observable, which is behavior I would have expected from switchmap.)在这个例子中,每个连续的文档点击都会输出一个结果(即“of”observable 的输出,这是我对 switchmap 的预期行为。)

In this example, switchMap and mergeMap have the same behavior.在此示例中, switchMapmergeMap具有相同的行为。

Here's some code you can play around with to see where the two differ.这里有一些代码你可以玩弄看看两者的不同之处。 Try this with both mergeMap , switchMap , and hey, why not try concatMap too?.试试这个mergeMapswitchMap ,嘿,为什么不试试concatMap呢?

// Lets pretend we're grabbing the date from a server. 
// The server is slow, so this takes 3 seconds
function getDate(): Observable<number> {
  return timer(3000).pipe(
    map(_ => Date.now())
  );
}

fromEvent(document, 'click').pipe(
  take(5),
  mergeMap(getDate)
).subscribe({
  next: x => console.log('value ', x),
  complete: () => console.log('done')
});

The difference:区别:

You'll notice how mergeMap keeps every incoming 'click' from the document.您会注意到 mergeMap 如何保留文档中的每个传入“点击”。 If you 'click' twice, there will be two calls to getDate and both calls will be kept active at the same time.如果您“单击”两次,将有两个调用getDate并且两个调用将同时保持活动状态。 3 seconds later the first call will complete and emit a number and then some moments later the second call will complete and emit a number. 3 秒后,第一个呼叫将完成并发出一个数字,然后过了一会儿,第二个呼叫将完成并发出一个数字。

Once you've clicked 5 times and all the resulting calls to getDate complete, you'll see your stream is done.单击 5 次并完成对getDate的所有调用后,您将看到您的流已完成。

Try this with switchMap and you'll notice that if you click more than once in a 3 second interval, the oldest click is dropped in favor of the newest one.switchMap试试这个,你会注意到如果你在 3 秒的时间间隔内点击不止一次,最旧的点击会被最新的点击所取代。 switchMap will only allow a single inner observable (call to getDate) to be active at a time, so it cancels old ones and switches over to the new ones. switchMap只允许一个内部 observable(调用 getDate)处于活动状态,因此它会取消旧的并切换到新的。


This all becomes more interesting when dealing with observables that emit more than once.当处理多次发射的可观察对象时,这一切都变得更加有趣。


A quick note:快速说明:

the merge in mergeMap doesn't mean "merge the items these streams emit." mergeMap中的合并并不意味着“合并这些流发出的项目”。 It means "merge these streams".它的意思是“合并这些流”。

An update:更新:

Part 1 (of 3) By preserve I mean that of() returns a distinct observable that can be subscribed to.第 1 部分(共 3 部分) 保留我的意思是 of() 返回一个可以订阅的不同的 observable。 eg I can do "of("value").subscribe(val => console.log(val))" to give output value.例如,我可以执行 "of("value").subscribe(val => console.log(val))" 来提供输出值。 If I repeat subscribtions to that same observable I will get repeated identical outputs.如果我重复订阅同一个 observable,我将得到重复的相同输出。 If that same observable is then placed as the inner observable of a mergemap() why isnt it preserved on subsequent invocations?如果将相同的可观察对象放置为 mergemap() 的内部可观察对象,为什么在后续调用中不保留它?

For each value emitted by the source observable, mergeMap returns a new inner observable exactly once.对于源 observable 发出的每个值,mergeMap 只返回一个新的内部 observable 一次。

These two observables have the same behavior:这两个 observables 具有相同的行为:


// Turn 3 observables into a single observable.
// merged$ will (as a single stream) emit all the values
// that it's inner streams emit)
const merged$ = merge(
  functionReturningObservable(1),
  functionReturningObservable(2),
  functionReturningObservable(3)
)


// Turn the 3 values from the source observables into merged
// inner observables.
// merged$ will (as a single stream) emit all the values
// that it's inner streams emit)
const merged$ = from([1,2,3]).pipe(
  mergeMap(v => functionReturningObservable(v))
)

Part 2 (orf 3) Why am I not seeing an accumulation of "of" observables every time I click the mouse in the example above?第 2 部分 (orf 3) 为什么在上面的示例中每次单击鼠标时都没有看到“of”可观察对象的累积? if mergemap merges streams where did the previous stream go?如果 mergemap 合并流,前一个流去哪里了? If I replace the inner observable "of" with an "interval it behaves exactly as I would expect and each click accumulates a new interval observable stream.如果我用“间隔”替换内部可观察“的”,它的行为与我预期的完全一样,并且每次点击都会累积一个新的间隔可观察流。

If of("value") emits one value and completes.如果of("value")发出一个值并完成。 If merged with another stream, of("value") still just emits a single value and completes.如果与另一个流合并, of("value")仍然只发出一个值并完成。 interval emits many values and never completes, so you should expect any observable that an interval is merged into to emit many values and never complete. interval发出许多值并且永远不会完成,因此您应该期望将间隔合并到的任何可观察对象发出许多值并且永远不会完成。

merge(
  interval(50).pipe(take(5)),
  of("hello world"),
).subscribe(console.log);

emits:发出:

"hello world"
0
1
2
3
4

Neither merge nor mergeMap will unsubscribe and then resubscribe to an inner observable. mergemergeMap都不会unsubscribe然后resubscribe内部 observable。 They both subscribe once and will let the inner observable naturally complete.他们都订阅了一次,并且会让内部的 observable 自然完成。

Part 3(of 3) You said "Because mergeMap merges, it will not drop the date.".第 3 部分(共 3 部分)您说“因为 mergeMap 合并,它不会删除日期。”。 This post exists because mergmap appears to be dropping the date of the previous invocation, where I expected an accumulation of streams (as seen if I replace of with interval)这篇文章的存在是因为 mergmap 似乎正在删除上一次调用的日期,我预计会在该日期累积流(如果我用间隔替换 of 则可以看到)

If you're not seeing the date emitted on the merged observable, then there's likely a bug in your code.如果您没有看到合并的 observable 上发出的日期,那么您的代码中可能存在错误。 While you shouldn't see a date re-emitted, you should still see it once the same way you would with the inner observable on it's own (that's what it means to merge).虽然您不应该看到重新发出的日期,但您仍然应该像使用内部可观察对象一样看到它一次(这就是合并的意思)。


Here's a new operator that resubscribes to every inner observable every time a new value is emitted by the source.这是一个新的运算符,每次源发出一个新值时,它都会重新订阅每个内部可观察对象。 Not sure if that's helpful, but in case you'd like to play around with the differences:不确定这是否有帮助,但如果您想尝试不同之处:

function resubscribingMergeMap<T, R>(
  project: (v: T) => Observable<R>
): OperatorFunction<T, R>{

  return pipe(
    map(project),
    scan(
      (acc, curr) => [curr, ...acc], 
      [] as Observable<R>[]
    ),
    mergeMap(vs => merge(...vs))
  );

}

// Here it is in use with your earlier example:
fromEvent(document, 'click').pipe(
  take(5),
  resubscribingMergeMap(_ => of(Date.now()))
).subscribe({
  next: x => console.log('value ', x),
  complete: () => console.log('done')
});

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM