Làm thế nào để làm cho một chuỗi có thể quan sát đợi một chuỗi khác hoàn thành trước khi phát?


87

Giả sử tôi có một Observable, như vậy:

var one = someObservable.take(1);

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

Sau đó, tôi có một giây Observable:

var two = someOtherObservable.take(1);

Bây giờ, tôi muốn subscribe()đến two, nhưng tôi muốn chắc chắn rằng oneđã hoàn thành trước khi twothuê bao bị sa thải.

Tôi có thể sử dụng loại phương pháp đệm nào twođể làm cho phương thức thứ hai đợi phương thức đầu tiên hoàn thành?

Tôi cho rằng tôi đang muốn tạm dừng twocho đến khi onehoàn tất.


1
Tôi tin rằng câu trả lời cho điều này là phương thức .exhaustMap () tuy nhiên tôi sẽ không giả vờ biết cách triển khai nó - mô tả đầy đủ ở đây: blog.angular-university.io/rxjs-higher-order-mapping
Peter Nixey

Câu trả lời:


53

Một vài cách tôi có thể nghĩ ra

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
Tôi đã kết thúc bằng cách sử dụng pauseresumethay vì publishconnect, nhưng ví dụ hai về cơ bản là tuyến đường tôi đã chọn.
Stephen

1
Phương thức này có luôn luôn onegiải quyết ( ) quan sát được đầu tiên trước ( ) thứ hai twobên trong hàm subscribe () - không?
John

Tại sao không sử dụng Observable.forkJoin()? Xem liên kết này learningrxjs.io/operators/combination/forkjoin.html
mspasiuk

16
@mspasiuk theo yêu cầu của OP, họ chỉ muốn người thứ hai đăng ký sau khi người đầu tiên hoàn thành. forkJoinđăng ký đồng thời.
paulpdaniels

17

Nếu bạn muốn đảm bảo rằng thứ tự thực hiện được giữ lại, bạn có thể sử dụng flatMap làm ví dụ sau

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

Kết quả sẽ là:

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

15

bỏ quaUntil () với cuối cùng ()

bỏ qua: bỏ qua các mục đã phát cho đến khi một mục khác có thể quan sát được đã phát ra

cuối cùng: phát ra giá trị cuối cùng từ một chuỗi (tức là đợi cho đến khi nó hoàn thành rồi phát ra)

Lưu ý rằng bất kỳ thứ gì phát ra từ có thể quan sát được chuyển đến skipUntilsẽ hủy bỏ việc bỏ qua, đó là lý do tại sao chúng ta cần thêm last()- để đợi luồng hoàn tất.

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

Chính thức: https://rxjs-dev.firebaseapp.com/api/operators/skipUntil


Sự cố có thể xảy ra: Lưu ý rằng last()bản thân nó sẽ lỗi nếu không có gì được phát ra. Các last()nhà điều hành không có một defaulttham số nhưng chỉ khi được sử dụng kết hợp với một vị ngữ. Tôi nghĩ nếu tình huống này là vấn đề đối với bạn (nếu sequence2$có thể hoàn thành mà không phát ra) thì một trong những điều này sẽ hoạt động (hiện chưa được kiểm tra):

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

Lưu ý rằng đó undefinedlà một mục hợp lệ để được phát ra, nhưng thực tế có thể là bất kỳ giá trị nào. Cũng lưu ý rằng đây là đường ống được gắn vào sequence2$chứ không phải main$đường ống.


Bản demo rất vụng về: angular-vgznak.stackblitz.io Bạn cần phải bấm vào để mở khay console
Simon_Weaver

Cú pháp của bạn sai. Bỏ quaUntil không thể được đính kèm trực tiếp vào một đối tượng có thể quan sát được nếu không bạn sẽ gặp lỗi sau: 'Thuộc tính' bỏ qua cho đến khi 'không tồn tại trên loại' Có thể quan sát <any> '.' Trước tiên, bạn cần chạy nó qua .pipe ()
London804

Có, đây là một câu trả lời cũ trước khi đường ống được yêu cầu. Cảm ơn vì đã đề cập đến nó. Tôi muốn cập nhật nó ngay bây giờ nhưng tôi đang sử dụng điện thoại của mình. Vui lòng chỉnh sửa câu trả lời.
Simon_Weaver

13

Đây là một khả năng khác tận dụng bộ chọn kết quả của 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 */ 
  });

Vì bộ chọn kết quả của switchMap đã bị mất giá trị nên đây là phiên bản cập nhật

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

8

Đây là một cách có thể tái sử dụng để làm điều đó (nó là kiểu chữ nhưng bạn có thể điều chỉnh nó cho phù hợp với js):

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

và bạn có thể sử dụng nó như bất kỳ toán tử nào:

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

Về cơ bản, nó là một toán tử xác định đăng ký trên nguồn có thể quan sát được cho đến khi tín hiệu có thể quan sát phát ra sự kiện đầu tiên.


6

Nếu thiết bị quan sát thứ hai bị nóng , có một cách khác để tạm dừng / tiếp tục :

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

Ngoài ra, bạn có thể sử dụng phiên bản đệm pausableBuffered để giữ dữ liệu trong khi tạm dừng được bật.


2

Đây là một cách khác, nhưng tôi cảm thấy đơn giản và trực quan hơn (hoặc ít nhất là tự nhiên nếu bạn đã quen với Lời hứa), hãy tiếp cận. Về cơ bản, bạn tạo một Observable bằng cách sử dụng Observable.create()để bọc onetwonhư một Observable duy nhất. Điều này rất giống với cách Promise.all()có thể hoạt động.

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();
        }
      );
    }
  );
});

Vậy, chuyện gì đang xảy ra ở đây? Đầu tiên, chúng tôi tạo một Observable mới. Hàm được truyền đến Observable.create(), được đặt tên phù hợp onSubscription, được chuyển cho trình quan sát (được xây dựng từ các tham số bạn truyền vào subscribe()), tương tự resolvevà được rejectkết hợp thành một đối tượng duy nhất khi tạo một Lời hứa mới. Đây là cách chúng tôi làm cho điều kỳ diệu hoạt động.

Trong onSubscription, chúng tôi đăng ký vào Observable đầu tiên (trong ví dụ trên, điều này được gọi là one). Cách chúng tôi xử lý nexterrortùy thuộc vào bạn, nhưng nói chung, mặc định được cung cấp trong mẫu của tôi phải phù hợp. Tuy nhiên, khi chúng tôi nhận được completesự kiện, nghĩa onelà bây giờ đã hoàn tất, chúng tôi có thể đăng ký có thể quan sát tiếp theo; do đó kích hoạt Observable thứ hai sau khi thứ nhất hoàn thành.

Trình quan sát mẫu được cung cấp cho Trình quan sát thứ hai khá đơn giản. Về cơ bản, secondbây giờ hoạt động giống như những gì bạn mong đợi twođể hoạt động như trong OP. Cụ thể hơn, secondsẽ phát ra giá trị đầu tiên và duy nhất được phát ra bởi someOtherObservable(because of take(1)) và sau đó hoàn thành, giả sử không có lỗi.

Thí dụ

Đây là một ví dụ đầy đủ, hoạt động, bạn có thể sao chép / dán nếu bạn muốn xem ví dụ của tôi hoạt động trong đời thực:

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!");
  }
);

Nếu bạn xem bảng điều khiển, ví dụ trên sẽ in:

1

6

Làm xong!


Đây là bước đột phá mà tôi cần để tạo toán tử 'cụm (T, X, D)' tùy chỉnh của riêng mình, chỉ xử lý X đầu tiên phát ra trong khoảng thời gian T từ nguồn và phát ra kết quả cách nhau bởi độ trễ D. Cảm ơn bạn!
wonkim00

Tôi rất vui vì nó đã giúp ích, tôi cũng rất ngộ khi nhận ra điều này.
c1moore

2

Đây là một toán tử tùy chỉnh được viết bằng TypeScript chờ tín hiệu trước khi đưa ra kết quả:

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

Bạn có thể sử dụng nó như thế này:

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

1
mẫu đẹp! Bạn thậm chí có thể làm Three.pipe (waitFor (một), waitFor (hai), lấy (1))
David Rinck

1

tốt, tôi biết điều này khá cũ nhưng tôi nghĩ rằng những gì bạn có thể cần là:

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

Bạn có thể sử dụng kết quả được tạo ra từ Observable trước đó nhờ toán tử mergeMap (hoặc bí danh flatMap ) như sau:

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

từ đây: learningrxjs.io/learn-rxjs/operators/transformation/mergemap - "Nếu thứ tự phát xạ và đăng ký các vật quan sát bên trong là quan trọng, hãy thử concatMap!"
gsziszi
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.