하나의 Observable 시퀀스가 ​​방출하기 전에 다른 시퀀스가 ​​완료 될 때까지 기다리는 방법은 무엇입니까?


93

다음 Observable과 같이.

var one = someObservable.take(1);

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

그런 다음 두 번째가 있습니다 Observable.

var two = someOtherObservable.take(1);

지금, 나는 원하는 subscribe()two,하지만 난이 있는지 확인하려면 one전과 완료 two가입자가 발생합니다.

two두 번째가 첫 번째가 완료 될 때까지 기다리도록하려면 어떤 종류의 버퍼링 방법을 사용할 수 있습니까?

완료 two될 때까지 일시 중지하려고합니다 one.


1
나는 이것에 대한 대답이 .exhaustMap () 메서드라고 믿지만 그것을 구현하는 방법을 아는 척하지는 않을 것입니다-여기에 전체 설명 : blog.angular-university.io/rxjs-higher-order-mapping
Peter Nixey

답변:


56

내가 생각할 수있는 몇 가지 방법

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
결국 pauseand resume대신 publishand connect를 사용했지만 예제 2는 본질적으로 내가 선택한 경로입니다.
Stephen

1
이 메서드 는 subscribe () 함수 내에서 one두 번째 ( two) 전에 항상 첫 번째 관찰 가능 ( )을 확인 합니까?
John

왜 사용하지 Observable.forkJoin()않습니까? 이 링크를 참조하십시오 learnrxjs.io/operators/combination/forkjoin.html
mspasiuk

18
@mspasiuk는 OP 요구 사항에 따라 첫 번째가 완료된 두 번째가 구독하기를 원했습니다 . forkJoin동시에 구독합니다.
paulpdaniels

18

실행 순서가 유지되는지 확인하려면 다음 예제와 같이 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"

16

last ()와 함께 skipUntil ()

skipUntil : 다른 Observable이 방출 될 때까지 방출 된 항목을 무시합니다.

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에 직접 연결할 수 없습니다. 그렇지 않으면 다음 오류가 발생합니다. 'Property'skipUntil '이'Observable <any> '유형에 없습니다.' 먼저 .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));

기본적으로 관찰 가능한 신호가 첫 번째 이벤트를 방출 할 때까지 관찰 가능한 소스에 대한 구독을 연기하는 연산자입니다.


이 재사용 가능한 함수의 rxswift 버전이
있습니까

6

두 번째 Observable이 hot 이면 일시 중지 / 재개 를 수행하는 다른 방법 이 있습니다 .

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

여기에 또 다른 것이 있지만 더 간단하고 직관적입니다 (또는 약속에 익숙하다면 적어도 자연 스럽습니다), 접근 방식입니다. 기본적으로 Observable.create()랩핑 onetwo단일 Observable로 사용하여 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())로 전달되며 , 이는 새 Promise를 만들 때 단일 객체 resolve와 유사 하고 reject결합됩니다. 이것이 우리가 마법을 작동시키는 방법입니다.

onSubscription 첫 번째 Observable을 구독합니다 (위의 예에서는라고 함 one). 우리가 처리하는 방법 nexterror당신에게 달려 있지만, 제 샘플에 제공된 기본값은 일반적으로 적절할 것입니다. 그러나 complete이벤트를 받으면 one이제 완료 되었음을 의미 합니다. 다음 Observable을 구독 할 수 있습니다. 따라서 첫 번째 Observable이 완료된 후 두 번째 Observable을 발사합니다.

두 번째 Observable에 제공된 예제 옵저버는 매우 간단합니다. 원래,second 이제 twoOP에서 예상 하는 것처럼 작동합니다. 더 구체적으로, 오류가 없다고 가정 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

끝난!


이것은 소스에서 시간 범위 T 내에 첫 번째 X 방출 만 처리하고 D 지연 간격으로 결과를 방출하는 고유 한 '클러스터 (T, X, D)'연산자를 만드는 데 필요한 획기적인 발전이었습니다. 감사합니다!
wonkim00 aug.

도움이 돼 기쁘고,이 사실을 깨달았을 때도 매우 깨달았습니다.
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 (일), WAITFOR (2 개), 테이크 (1)) 할도 수
데이비드 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

mergeMap (또는 그의 별칭 flatMap ) 연산자 덕분에 이전 Observable에서 내 보낸 결과를 다음과 같이 사용할 수 있습니다 .

 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))

1
여기에서 : learnrxjs.io/learn-rxjs/operators/transformation/mergemap- "내부 관찰 가능 항목의 방출 및 구독 순서가 중요하다면 concatMap을 사용해보세요!"
gsziszi
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.