如何使一个可观察序列在发射之前先等待另一个完成?


86

说我有一个Observable,像这样:

var one = someObservable.take(1);

one.subscribe(function(){ /* do something */ });

然后,我有第二个Observable

var two = someOtherObservable.take(1);

现在,我想这样subscribe()two,但是我想确保onetwo解雇该订阅者之前已完成该操作。

我可以使用哪种缓冲方法two使第二个等待第一个完成?

我想我要暂停two直到one完成。


1
我相信答案是.exhaustMap()方法,但是我不会假装知道如何实现它-完整说明在这里:blog.angular-university.io/rxjs-higher-order-mapping
Peter Nixey

Answers:


53

我能想到的几种方法

import {take, publish} from 'rxjs/operators'
import {concat} from 'rxjs'

//Method one

var one = someObservable.pipe(take(1));
var two = someOtherObservable.pipe(take(1));
concat(one, two).subscribe(function() {/*do something */});

//Method two, if they need to be separate for some reason
var one = someObservable.pipe(take(1));
var two = someOtherObservable.pipe(take(1), publish());
two.subscribe(function(){/*do something */});
one.subscribe(function(){/*do something */}, null, two.connect.bind(two));

1
我最终使用pauseresume而不是publishconnect,但是示例2本质上是我走的路线。
斯蒂芬

1
此方法是否会始终使第一个observable(one)解析在twosubscribe()函数内部的第二个()之前?
约翰·

为什么不使用Observable.forkJoin()?看到此链接learningrxjs.io/operators/combination/forkjoin.html
mspasiuk

16
@mspasiuk根据OP的要求,他们只希望第二个人在第一个人完成订阅。forkJoin同时订阅。
paulpdaniels

17

如果要确保保留执行顺序,可以使用flatMap作为以下示例

const first = Rx.Observable.of(1).delay(1000).do(i => console.log(i));
const second = Rx.Observable.of(11).delay(500).do(i => console.log(i));
const third = Rx.Observable.of(111).do(i => console.log(i));

first
  .flatMap(() => second)
  .flatMap(() => third)
  .subscribe(()=> console.log('finished'));

结果将是:

"1"
"11"
"111"
"finished"

15

skipUntil()与last()

skipUntil:忽略发出的项目,直到发出另一个可观察到的项目

last:从序列中发出最后一个值(即等待直到完成,然后发出)

请注意,传递给observable的所有内容都skipUntil将取消跳过,这就是为什么我们需要添加last()-等待流完成。

main$.skipUntil(sequence2$.pipe(last()))

官方:https : //rxjs-dev.firebaseapp.com/api/operators/skipUntil


可能的问题:请注意,如果不发出任何消息,则last()其本身将出错。该last()运营商确实有default配合使用的谓词参数,但只有当。我认为,如果这种情况对您来说是个问题(如果sequence2$可能会完成而没有出现),那么其中一种应该可以工作(当前未经测试):

main$.skipUntil(sequence2$.pipe(defaultIfEmpty(undefined), last()))
main$.skipUntil(sequence2$.pipe(last(), catchError(() => of(undefined))

请注意,这undefined是要发出的有效项目,但实际上可以是任何值。另请注意,这是连接到的管道,sequence2$而不是main$管道。


非常笨拙的演示:angular-vgznak.stackblitz.io您需要单击打开控制台托盘
Simon_Weaver

您的语法错误。skipUntil不能直接附加到可观察对象,否则会出现以下错误:'Observable <any>类型的属性'skipUntil'不存在。首先,您需要通过.pipe运行()
London804

是的,这是在需要管道之前的旧答案。感谢您提及。我现在要更新它,但是我在用手机。随时编辑答案。
Simon_Weaver

13

这是利用switchMap的结果选择器的另一种可能性

var one$ = someObservable.take(1);
var two$ = someOtherObservable.take(1);
two$.switchMap(
    /** Wait for first Observable */
    () => one$,
    /** Only return the value we're actually interested in */
    (value2, value1) => value2
  )
  .subscribe((value2) => {
    /* do something */ 
  });

由于switchMap的结果选择器已弃用,因此这里是更新版本

const one$ = someObservable.pipe(take(1));
const two$ = someOtherObservable.pipe(
  take(1),
  switchMap(value2 => one$.map(_ => value2))
);
two$.subscribe(value2 => {
  /* do something */ 
});

8

这是一种可重用的方式(它是打字稿,但您可以将其适应js):

export function waitFor<T>(signal: Observable<any>) {
    return (source: Observable<T>) =>
        new Observable<T>(observer =>
            signal.pipe(first())
                .subscribe(_ =>
                    source.subscribe(observer)
                )
        );
}

您可以像任何运算符一样使用它:

var two = someOtherObservable.pipe(waitFor(one), take(1));

基本上,它是一个运算符,将可延迟的订阅推迟到可观察的源,直到可观察的信号发出第一个事件。


6

如果第二个可观察对象很热,则有另一种方法可以暂停/恢复

var pauser = new Rx.Subject();
var source1 = Rx.Observable.interval(1000).take(1);
/* create source and pause */
var source2 = Rx.Observable.interval(1000).pausable(pauser);

source1.doOnCompleted(function () { 
  /* resume paused source2 */ 
  pauser.onNext(true);
}).subscribe(function(){
  // do something
});

source2.subscribe(function(){
  // start to recieve data 
});

您也可以使用缓冲版本pausableBuffered在暂停期间保持数据。


2

这是另一种方法,但我觉得这种方法更直接,更直观(如果您习惯了Promises,则至少是自然的)。基本上,您创建一个ObservableObservable.create()用来包装onetwo作为单个Observable。这与Promise.all()工作方式非常相似。

var first = someObservable.take(1);
var second = Observable.create((observer) => {
  return first.subscribe(
    function onNext(value) {
      /* do something with value like: */
      // observer.next(value);
    },
    function onError(error) {
      observer.error(error);
    },
    function onComplete() {
      someOtherObservable.take(1).subscribe(
        function onNext(value) {
          observer.next(value);
        },
        function onError(error) {
          observer.error(error);
        },
        function onComplete() {
          observer.complete();
        }
      );
    }
  );
});

那么,这是怎么回事?首先,我们创建一个新的Observable。传递给函数Observable.create(),恰如其名onSubscription,传递观察者(由你传递给参数建subscribe()),这是类似的resolve,并reject创造了新的承诺时,组合成一个单一的对象。这就是我们使魔术发挥作用的方式。

在中onSubscription,我们订阅了第一个Observable(在上面的示例中,这称为one)。我们如何处理nexterror取决于您,但是从总体上来讲,我的示例中提供的默认值应该是适当的。但是,当我们接收到该complete事件(即one已经完成)时,我们可以订阅下一个Observable。从而在第一个Observable完成后触发第二个Observable。

为第二个Observable提供的示例观察器非常简单。基本上,second现在的行为就像您希望two在OP中表现的那样。更具体地说,假设没有错误,second将发出由someOtherObservable(由于take(1))发出的第一个值,并且只有第一个值,然后完成。

这是一个完整的工作示例,如果您想看到我的示例在现实生活中可以工作,则可以复制/粘贴该示例:

var someObservable = Observable.from([1, 2, 3, 4, 5]);
var someOtherObservable = Observable.from([6, 7, 8, 9]);

var first = someObservable.take(1);
var second = Observable.create((observer) => {
  return first.subscribe(
    function onNext(value) {
      /* do something with value like: */
      observer.next(value);
    },
    function onError(error) {
      observer.error(error);
    },
    function onComplete() {
      someOtherObservable.take(1).subscribe(
        function onNext(value) {
          observer.next(value);
        },
        function onError(error) {
          observer.error(error);
        },
        function onComplete() {
          observer.complete();
        }
      );
    }
  );
}).subscribe(
  function onNext(value) {
    console.log(value);
  },
  function onError(error) {
    console.error(error);
  },
  function onComplete() {
    console.log("Done!");
  }
);

如果您观看控制台,将显示以上示例:

1个

6

做完了!


这是创建我自己的自定义“ cluster(T,X,D)”运算符所需的突破,该运算符仅处理从源到时间跨度T内的第一个X发射,并发射以D延迟间隔开的结果。谢谢!
wonkim00

我很高兴它有所帮助,当我意识到这一点时也非常有启发。
c1moore

2

这是一个用TypeScript编写的自定义运算符,它在发出结果之前等待信号:

export function waitFor<T>(
    signal$: Observable<any>
) {
    return (source$: Observable<T>) =>
        new Observable<T>(observer => {
            // combineLatest emits the first value only when
            // both source and signal emitted at least once
            combineLatest([
                source$,
                signal$.pipe(
                    first(),
                ),
            ])
                .subscribe(([v]) => observer.next(v));
        });
}

您可以像这样使用它:

two.pipe(waitFor(one))
   .subscribe(value => ...);

1
漂亮的图案!您甚至可以执行three.pipe(waitFor(one),waitFor(two),take(1))
David Rinck

1

好吧,我知道这已经很老了,但我认为您可能需要的是:

var one = someObservable.take(1);

var two = someOtherObservable.pipe(
  concatMap((twoRes) => one.pipe(mapTo(twoRes))),
  take(1)
).subscribe((twoRes) => {
   // one is completed and we get two's subscription.
})

0

您可以使用以前的Observable发出的结果,这要归功于mergeMap(或他的别名flatMap)运算符,如下所示:

 const one = Observable.of('https://api.github.com/users');
 const two = (c) => ajax(c);//ajax from Rxjs/dom library

 one.mergeMap(two).subscribe(c => console.log(c))

从这里开始:learnrxjs.io/learn-rxjs/operators/transformation/mergemap- “如果内部可观测对象的发射和订阅顺序很重要,请尝试concatMap!”
gsziszi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.