Rxjs 使用同步流管理可观察订阅 问题

Rxjs 使用同步流管理可观察订阅 问题,rxjs,Rxjs,如果一个可观察对象正在同步运行,那么在返回之前执行给subscribe的回调。结果是以下代码给出了一个错误。(子系统未初始化) 一种新的解决方案 如果我们强制流的值进入事件循环,我们就不再有这个问题订阅将始终在调用lambda之前返回 const sub = from([1,2,3,4,5]).pipe( delay(0) ).subscribe(x => { if(x > 3) sub.unsubscribe(); console.log(x); }); 然而,我觉得

如果一个可观察对象正在同步运行,那么在返回之前执行给
subscribe
的回调。结果是以下代码给出了一个错误。(子系统未初始化)

一种新的解决方案 如果我们强制流的值进入事件循环,我们就不再有这个问题<代码>订阅将始终在调用lambda之前返回

const sub = from([1,2,3,4,5]).pipe(
  delay(0)
).subscribe(x => {
  if(x > 3) sub.unsubscribe();
  console.log(x);
});
然而,我觉得这是个坏主意。如果没有其他原因,除了性能。尽管它也使运行顺序不那么确定(哪个浏览器?、节点?)

惯用的RxJS解决方案 不要自己退订,让接线员帮你退订

const unsub = new Subject();
from([1,2,3,4,5]).pipe(
  takeUntil(unsub)
).subscribe(x => {
  if(x > 3) {
    unsub.next();
    unsub.complete();
  }
  console.log(x);
});
这里的问题是,为了实现一个非常具体的目标,我们需要创建作为主题的整个装置。这就像买一辆卡车来获得车轮一样。它的伸缩性不好。最后,就像自己调用
unsubscribe()
一样,它仍然混合了命令式和函数式javascript

同样的问题在更大的范围内 考虑一个操作员,该操作员获取一个可观测值列表,并仅从列表中较早的可观测值中发射值,而不是之前的任何发射值

下面是这个操作符对事件循环的处理

function prefer<T, R>(...observables: Observable<R>[]): Observable<R>{
  return new Observable(observer => {

    const subscrptions = new Array<Subscription>();
    const unsub = (index) => {
      for(let i = index; i < subscrptions.length; i++){
        subscrptions[i].unsubscribe();
      }
    }

    observables.map(stream => stream.pipe(
      delay(0)
    )).forEach((stream, index) => 
      subscrptions.push(stream.subscribe(payload => {
        observer.next(payload);
        unsub(index + 1);
        subscrptions.length = index + 1;
      }))
    );

    return { unsubscribe: () => unsub(0) }
  })
}
这里还有正在使用的操作符

prefer(
  interval(10000).pipe(
    take(5),
    map(_ => "Every 10s")
  ),
  interval(5000).pipe(map(_ => "Every 5s")),
  interval(1000).pipe(map(_ => "Every 1s")),
  interval(250).pipe(map(_ => "Every 1/4s"))
).subscribe(console.log);
想象一下在比例上使用这个操作符。相对容易理解的是,第一种方法的内存占用比第二种方法小得多(O(n)vs O(n*n)内存使用)


最后问题 因为(在javascript中)同步代码在任何其他代码运行之前运行到完成,所以在订阅的同步部分返回之前,能够访问可观察的
订阅
似乎没有意义。然而,作为提前中止流的一种手段,似乎能够提前访问流的
订阅
可能有好处(至少在内存中)


有没有一种(相对)优雅的方法来测量一个可观测的物体来解决这些问题?

好的,我来回答我自己的问题。在研究这个问题太久之后,我偶然发现了一个事实,原来RxJS有一个非常好的内置解决方案。它之所以非常好,是因为它使用了
publish/connect
,这似乎是通过主题内部实现的(尽管内存占用仍然更好?不知道为什么)

这不是发布/连接的预期用途,因为我不是多播。关键是可连接的可观察对象不是以
subscribe
开头,而是以
connect
开头

您可以使用它获得所需的行为,而完全不依赖事件循环

使用发布的解决方案 小型示例:

const stream = publish()(from([1,2,3,4,5]));

const sub = stream.subscribe(x => {
  if(x > 3) sub.unsubscribe();
  console.log(x);
});

stream.connect();
缩放到自定义运算符:

function prefer<T, R>(...observables: Observable<R>[]): Observable<R>{
  return new Observable(observer => {

    const subscrptions = new Array<Subscription>();
    const unsub = (index = 0) => {
      for(let i = index; i < subscrptions.length; i++){
        subscrptions[i].unsubscribe();
      }
    }

    observables
      .map(stream => publish()(stream))
      .map((stream, index) => {
        subscrptions.push(stream.subscribe((payload: R) => {
          observer.next(payload);
          unsub(index + 1);
          subscrptions.length = index + 1;
        }));
        return stream;
      })
      .forEach(stream => stream.connect());

    return { unsubscribe: () => unsub() }
  })
}
输出:
“订阅前”“订阅后”1 2 3 4

关于使用connect,如下所示:

const stream = from([1,2,3,4,5]).pipe(
  delay(0)
);

console.log("before subscribe");
const sub = stream.subscribe(x => {
  if(x > 3) sub.unsubscribe();
  console.log(x);
});
console.log("after subscribe");
const stream = publish()(from([1,2,3,4,5]));

const sub = stream.subscribe(x => {
  if(x > 3) sub.unsubscribe();
  console.log(x);
});

console.log("before connect");
stream.connect();
console.log("after connect");
输出:
“连接前”1 2 3 4“连接后”


由于connect保持同步观测值,
unsubscribe
仍然可以同步发生,并且在运行
connect
后的行之前处理整个观测值流。这是一个相当大的胜利。

这是一个非常有趣的问题

这里有另一种方法,仅适用于RxJS 6.x

const sub=from([1,2,3,4,5])。订阅(函数(x){
如果(x>3),则此。取消订阅();
控制台日志(x);
});
/*
1.
2.
3.
4.
*/
这是因为
subscribe
的回调将被分配给,这将在使用
.subscribe
时创建

function prefer<T, R>(...observables: Observable<R>[]): Observable<R>{
  return defer(() => {

    const wUnsub = observables.map((stream, index) => ({
      stream: stream.pipe(
        map(payload => ({index, payload}))
      ), 
      unsub: new Subject()
    }));

    const unsub = (index) => {
      for(let i = index; i < wUnsub.length; i++){
        wUnsub[i].unsub.next();
        wUnsub[i].unsub.complete();
      }
    }
    
    return merge(...wUnsub.map(build => build.stream.pipe(
      takeUntil(build.unsub)
    ))).pipe(
      tap(({index}) => {
        unsub(index + 1);
        wUnsub.length = index + 1;
      }),
      map(({payload}) => payload),
      finalize(() => unsub(0))
    );
  });
}
let context:any=this;
...
next=(void)>observer或next);
...
这._context=context;
这个。_next=next;
...
然后,当调用
Subscriber
next
方法时,它将在引擎盖下发生,其中
此上下文
指的是当前
SafeSubscriber
实例。当被调用时,它将最终导致整个订阅链取消订阅


但是,在RxJS 7中,这有一个不同的实现,因此这种方法将不再有效。

我理解,当你的值大于3时,你想要取消订阅一个可观察的

const sub = from([1,2,3,4,5]).subscribe(x => {
  if(x > 3) sub.unsubscribe();
  console.log(x);
});
你真的需要订阅吗? 我宁愿使用
takeWhile
rxjs操作符

from([1,2,3,4,5]).pipe(
  takeWhile(x <= 3),
).subscribe(x => console.log(x));
来自([1,2,3,4,5])管道(
takeWhile(x console.log(x));

take
/
takeWhile
在第一个例子中很简单的情况下工作得很好,但是这能帮助我实现上面的
preference
操作符吗?我不知道怎么做。一旦你管理一组流,并且根据它们的合并值取消订阅,那么在我看来,
take
/
takeWhile不再具有足够的表达能力。这很酷。从未考虑过回调的上下文,因为我习惯使用lambda表示法。@MrkSef是的,我一直支持匿名函数,但有时需要更改:D
from([1,2,3,4,5]).pipe(
  takeWhile(x <= 3),
).subscribe(x => console.log(x));