จะทำให้ลำดับที่สังเกตได้หนึ่งอันต้องรอให้อีกลำดับหนึ่งเสร็จสมบูรณ์ก่อนที่จะเปล่งออกมา?


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
ฉันเชื่อว่าคำตอบนี้คือเมธอด. expeMap () แต่ฉันจะไม่แสร้งทำเป็นว่ารู้วิธีการใช้งาน - คำอธิบายแบบเต็มที่นี่: blog.angular-university.io/rxjs-higher-order-mapping
Peter

คำตอบ:


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
ฉันลงเอยด้วยการใช้pauseและresumeแทนที่จะเป็นpublishและconnectแต่ตัวอย่างที่สองเป็นเส้นทางที่ฉันใช้
Stephen

1
วิธีนี้จะทำให้ค่าแรกที่สังเกตได้ ( one) แก้ไขก่อนtwoฟังก์ชันที่สอง ( ) ภายในฟังก์ชัน subscribe () - หรือไม่
ยอห์น

ทำไมไม่ใช้Observable.forkJoin()? ดูลิงค์นี้learnrxjs.io/operators/combination/forkjoin.html
mspasiuk

18
@mspasiuk ตามข้อกำหนด OPs พวกเขาต้องการให้คนที่สองสมัครสมาชิกหลังจากครั้งแรกเสร็จสมบูรณ์ 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

skipUntil () กับ last ()

skipUntil: ละเว้นรายการที่ปล่อยออกมาจนกว่าจะมีการปล่อยรายการอื่นที่สังเกตได้

last: ปล่อยค่าสุดท้ายจากลำดับ (เช่นรอจนกว่าจะเสร็จสิ้นจากนั้นจึงปล่อย)

โปรดทราบว่าสิ่งที่ปล่อยออกมาจากสิ่งที่สังเกตได้ที่ส่งผ่านไปskipUntilจะยกเลิกการข้ามซึ่งเป็นสาเหตุที่เราต้องเพิ่มlast()- เพื่อรอให้สตรีมเสร็จสมบูรณ์

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

เป็นทางการ: https://rxjs-dev.firebaseapp.com/api/operators/skipUntil


ปัญหาที่เป็นไปได้: โปรดทราบว่าจะเกิดข้อผิดพลาดlast()เองหากไม่มีสิ่งใดถูกปล่อยออกมา ตัวดำเนินการมีพารามิเตอร์ แต่เฉพาะเมื่อใช้ร่วมกับเพรดิเคต ฉันคิดว่าหากสถานการณ์นี้เป็นปัญหาสำหรับคุณ (หากอาจเสร็จสมบูรณ์โดยไม่เปล่งแสง) หนึ่งในนั้นควรใช้งานได้ (ยังไม่ได้ทดสอบ):last()defaultsequence2$

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 'ในประเภท' สังเกตได้ <any> '' ก่อนอื่นคุณต้องเรียกใช้ผ่าน. pip ()
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

นี่เป็นวิธีที่ใช้ซ้ำได้ (เป็น typescript แต่คุณสามารถปรับเป็น 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 ของฟังก์ชันที่ใช้ซ้ำได้หรือไม่
sujith1406

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

นี่เป็นอีกเรื่องหนึ่ง แต่ฉันรู้สึกตรงไปตรงมาและใช้งานง่ายกว่า (หรืออย่างน้อยก็เป็นธรรมชาติถ้าคุณคุ้นเคยกับคำสัญญา) เข้าหา โดยพื้นฐานแล้วคุณจะสร้าง Observable โดยใช้Observable.create()เพื่อห่อหุ้มoneและtwoเป็น 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()ชื่อ aptly onSubscriptionจะถูกส่งผ่านผู้สังเกตการณ์ (สร้างขึ้นจากพารามิเตอร์ที่คุณส่งผ่านไปsubscribe()) ซึ่งคล้ายกับresolveและrejectรวมกันเป็นออบเจ็กต์เดียวเมื่อสร้าง Promise ใหม่ นี่คือวิธีที่เราทำให้เวทมนตร์ทำงานได้

ในonSubscriptionเราสมัครสังเกตแรก (ในตัวอย่างข้างต้นนี้ถูกเรียกว่าone) วิธีที่เราจัดการnextและerrorขึ้นอยู่กับคุณ แต่ค่าเริ่มต้นที่ให้ไว้ในตัวอย่างของฉันควรจะเหมาะสมโดยทั่วไปพูด อย่างไรก็ตามเมื่อเราได้รับcompleteกิจกรรมซึ่งหมายความว่าoneเสร็จสิ้นแล้วเราสามารถสมัครรับ 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

เสร็จแล้ว!


นี่คือความก้าวหน้าที่ฉันต้องการในการสร้างโอเปอเรเตอร์ 'คลัสเตอร์ (T, X, D)' ที่กำหนดเองซึ่งประมวลผลเฉพาะ X ตัวแรกที่เปล่งออกมาภายในช่วงเวลา T จากแหล่งที่มาและแสดงผลลัพธ์โดยเว้นระยะห่างด้วยความล่าช้า 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))

1
จากที่นี่: learnrxjs.io/learn-rxjs/operators/transformation/mergemap - "หากลำดับของการปล่อยและการสมัครใช้งานสิ่งที่สังเกตได้ภายในมีความสำคัญให้ลองใช้ concatMap!"
gsziszi
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.