简体   繁体   English

Rxjs:带有 takeUntil(timer) 的 Observable 在计时器滴答后继续发射

[英]Rxjs: Observable with takeUntil(timer) keeps emitting after the timer has ticked

I have run into a very strange behavior of takeUntil() .我遇到了takeUntil()一个非常奇怪的行为。 I create an observable timer:我创建了一个可观察的计时器:

let finish = Observable.timer(3000);

Then I wait for some time and call然后我等了一段时间再打电话

// 2500 ms later
someObservable.takeUntil(finish);

I would expect said observable to stop emitting after the timer "ticks", ie about 500ms after its creation.我希望上述 observable 在计时器“滴答”后停止发射,即在其创建后约 500 毫秒。 In reality it keeps emitting for 3000ms after its creation , way beyond the moment when the timer "ticks".实际上,它在创建后会持续发射 3000 毫秒,远远超过计时器“滴答”的那一刻。 This does not happen if I create the timer with a Date object containing absolute time value.如果我使用包含绝对时间值的 Date 对象创建计时器,则不会发生这种情况。

Is this by design?这是故意的吗? If yes, what is the explanation?如果是,解释是什么?

Here's complete code, runnable with node.js (it requires npm install rx ):这是完整的代码,可以使用 node.js 运行(它需要npm install rx ):

let {Observable, Subject} = require("rx")
let start = new Date().getTime();
function timeMs() { return new Date().getTime() - start };

function log(name, value) { 
    console.log(timeMs(), name, value);
}

Observable.prototype.log = function(name) {
    this.subscribe( v=>log(name,v), 
                    err=>log(name, "ERROR "+err.message), 
                    ()=>log(name, "DONE"));
    return this;
}

let finish = Observable.timer(3000).log("FINISH");
setTimeout( ()=>Observable.timer(0,500).takeUntil(finish).log("seq"), 2500);

This generates the following output:这将生成以下输出:

2539 'seq' 0
3001 'FINISH' 0
3005 'FINISH' 'DONE'
3007 'seq' 1
3506 'seq' 2
4006 'seq' 3
4505 'seq' 4
5005 'seq' 5
5506 'seq' 6
5507 'seq' 'DONE'

If I create the timer using absolute time:如果我使用绝对时间创建计时器:

let finish = Observable.timer(new Date(Date.now()+3000)).log("FINISH");

Then it behaves as expected:然后它按预期运行:

2533 'seq' 0
3000 'seq' 'DONE'
3005 'FINISH' 0
3005 'FINISH' 'DONE'

This behavior seems to be rather consistent in various situations.Eg if you take an interval and create child sequences using mergeMap() or switchMap() , the result would be similar: child sequences keep emitting beyond the finish event.这种行为在各种情况下似乎相当一致。例如,如果您使用mergeMap()switchMap()创建一个间隔并创建子序列,结果将是相似的:子序列在完成事件之后继续发射。

Thoughts?想法?

You are forgetting the first rule of cold Observables : Each subscription is a new stream.你忘记了冷Observables的第一条规则:每个订阅都是一个新的流。

Your log operator has a bug;您的log操作员有错误; it is subscribing once to the Observable (thus creating the first subscription) and then returning the original Observable , which get subscribed to again , implicitly, when you pass it to the takeUntil operator.它订阅Observable一次(从而创建第一个订阅),然后返回原始Observable ,当您将其传递给takeUntil运算符时,该Observable隐式地再次订阅。 Thus in reality you actually have two seq streams active, both of which are behaving correctly.因此,实际上您实际上有两个seq流处于活动状态,它们都运行正常。

It works in the absolute case, because you are basically setting each stream to emit at a specific time, not a relative time to when the subscription occurs.它适用于绝对情况,因为您基本上将每个流设置为在特定时间发出,而不是订阅发生的相对时间。

If you want to see it work I would suggest you change your implementation to:如果您想看到它的工作,我建议您将实现更改为:

let start = new Date().getTime();
function timeMs() { return new Date().getTime() - start };

function log(name, value) { 
    console.log(timeMs(), name, value);
}

Observable.prototype.log = function(name) {
    // Use do instead of subscribe since this continues the chain
    // without directly subscribing.
    return this.do(
      v=>log(name,v), 
      err=>log(name, "ERROR "+err.message), 
      ()=>log(name, "DONE")
    );
}

let finish = Observable.timer(3000).log("FINISH");

setTimeout(()=> 
  Observable.timer(0,500)
    .takeUntil(finish)
    .log("seq")
    .subscribe(), 
2500);

For completeness, here's the code that actually does what I wanted.为了完整起见,这里是实际执行我想要的代码。 By using Observable.publish().connect() it creates a "hot" timer that starts ticking immediately, and keeps the same time for all subscribers.通过使用Observable.publish().connect()它创建了一个“热”计时器,立即开始计时,并为所有订阅者保持相同的时间。 It also avoid unwanted subscriptions in the "log" method, as suggested by @paulpdaniels.正如@paulpdaniels 所建议的那样,它还可以避免在“日志”方法中进行不需要的订阅。

Warning: beware of the race condition.警告:注意竞争条件。 If the child sequence starts after the timer has ticked, it will never stop.如果子序列在计时器计时后开始,它将永远不会停止。 To demonstrate, change timeout in the last line from 2500 to 3500.为了演示,将最后一行中的超时从 2500 更改为 3500。

let {Observable, Subject, Scheduler, Observer} = require("rx")
let start = new Date().getTime();
function timeMs() { return new Date().getTime() - start };

function log(name, value) { 
    console.log(timeMs(), name, value);
}

var logObserver =  function(name) {
    return Observer.create( 
      v=>log(name,v), 
      err=>log(name, "ERROR "+err.message), 
      ()=>log(name, "DONE"));
}

Observable.prototype.log = function(name) { return this.do(logObserver(name)); }

Observable.prototype.start = function() { 
    var hot = this.publish(); hot.connect(); 
    return hot; 
}

let finish = Observable.timer(3000).log("FINISH").start();

setTimeout(()=> 
  Observable.timer(0,500)
    .takeUntil(finish)
    .log("seq")
    .subscribe(), 
2500);

The output is输出是

2549 'seq' 0
3002 'FINISH' 0
3006 'seq' 'DONE'
3011 'FINISH' 'DONE'

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

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