How to 'wait' for two observables in RxJS

前端 未结 7 1520
温柔的废话
温柔的废话 2020-12-13 12:14

In my app i have something like:

this._personService.getName(id)
      .concat(this._documentService.getDocument())
      .subscribe((response) => {
              


        
相关标签:
7条回答
  • 2020-12-13 12:24

    Last Update: May, 2020.

    combineLatest(observables)

    From reactiveX documentation:

    Whenever any input Observable emits a value, it computes a formula using the latest values from all the inputs, then emits the output of that formula.

    (Update: May, 2020) While the other example remains valid, here is a new syntax:

    // Observables to combine
    const name$ = this._personService.getName(id);
    const document$ = this._documentService.getDocument();
    
    name$.combineLatest(document$, (name, document) => {name, document})
        .subscribe(pair => {
               this.name = pair.name;
               this.document = pair.document;
               this.showForm();
           })
    

    combineLatest(observables) (alternate syntax):

    // Observables to combine
    const name$ = this._personService.getName(id);
    const document$ = this._documentService.getDocument();
    
    combineLatest(name$, document$, (name, document) => ({name, document}))
        .subscribe(pair => {
               this.name = pair.name;
               this.document = pair.document;
               this.showForm();
           })
    

    zip vs combineLatest

    (Update: Oct, 2018) I previously suggested the use of zip method. However, for some use cases, combineLatest has a few advantages over zip. So it is important to understand the differences.

    CombineLatest emits the latest emitted values from observables. While zip method emits the emitted items in sequence order.

    For example if observable #1 emits its 3rd item and observable #2 has emitted its 5th item. The result using zip method will be the 3rd emitted values of both observables.

    In this situation the result using combineLatest will be the 5th and 3rd. which feels more natural.


    Observable.zip(observables)

    (Original answer: Jul, 2017) Observable.zip method is explained in reactiveX documentation:

    Combines multiple Observables to create an Observable whose values are calculated from the values, in order, of each of its input Observables.

    // Observables to combine
    const name$ = this._personService.getName(id);
    const document$ = this._documentService.getDocument();
    
    Observable
        .zip(name$, document$, (name: string, document: string) => ({name, document}))
        .subscribe(pair => {
               this.name = pair.name;
               this.document = pair.document;
               this.showForm();
           })
    

    a side note (applies for both methods)

    The last parameter, where we have provided a function, (name: string, document: string) => ({name, document}) is optional. You can skip it, or do more complex operations:

    If the latest parameter is a function, this function is used to compute the created value from the input values. Otherwise, an array of the input values is returned.

    So if you skip the last part, you get an array:

    // Observables to combine
    const name$ = this._personService.getName(id);
    const document$ = this._documentService.getDocument();
    
    Observable
        .zip(name$, document$)
        .subscribe(pair => {
               this.name = pair['0'];
               this.document = pair['1'];
               this.showForm();
           })
    
    0 讨论(0)
  • 2020-12-13 12:24

    The RxJS Operators for Dummies: forkJoin, zip, combineLatest, withLatestFrom helped me a lot. As the name states it describes the following combination operators:

    • ForkJoin
    • zip
    • combineLatest
    • withLatestFrom

    Any of them could be the thing you are looking for, depends on the case. Check the article for more info.

    0 讨论(0)
  • 2020-12-13 12:25

    Improvement of Hamid Asghari answer which use direct arguments decomposition and automatically add types (when you use typescript)

    const name$ = this._personService.getName(id);
    const document$ = this._documentService.getDocument();
    
    combineLatest([name$, document$]).subscribe(([name, document]) => {
        this.name = name;
        this.document = document;
        this.showForm();
    });

    BONUS: You can also handle errors using above approach as follows

    import { combineLatest, of } from 'rxjs';
    //...
    
    const name$ = this._personService.getName(id);
    const document$ = this._documentService.getDocument();
    
    combineLatest([
      name$.pipe(     catchError( () => of(null as string  ) ) ), 
      document$.pipe( catchError( () => of(null as Document) ) ), // 'Document' is arbitrary type
    ]).subscribe(([name, document]) => {
        this.name = name;          // or null if error
        this.document = document;  // or null if error
        this.showForm();
    });

    0 讨论(0)
  • 2020-12-13 12:29

    For me this sample was best solution.

    const source = Observable.interval(500);
    const example = source.sample(Observable.interval(2000));
    const subscribe = example.subscribe(val => console.log('sample', val));
    

    So.. only when second (example) emit - you will see last emited value of first (source).

    In my task, I wait form validation and other DOM event.

    0 讨论(0)
  • 2020-12-13 12:34

    Use forkJoin() method of observables. Check this link for reference

    From the RXJS docs

    This operator is best used when you have a group of observables and only care about the final emitted value of each. One common use case for this is if you wish to issue multiple requests on page load (or some other event) and only want to take action when a response has been receieved for all. In this way it is similar to how you might use Promise.all

    Observable.forkJoin([character, characterHomeworld]).subscribe(results => {
      // results[0] is our character
      // results[1] is our character homeworld
      results[0].homeworld = results[1];
      this.loadedCharacter = results[0];
    });
    

    Code taken from: https://coryrylan.com/blog/angular-multiple-http-requests-with-rxjs

    0 讨论(0)
  • 2020-12-13 12:40

    You can use 'zip' or 'buffer' like the following.

    function getName() {
        return Observable.of('some name').delay(100);
    }
    
    function getDocument() {
        return Observable.of('some document').delay(200);
    }
    
    // CASE1 : concurrent requests
    Observable.zip(getName(), getDocument(), (name, document) => {
        return `${name}-${document}`;
    })
        .subscribe(value => console.log(`concurrent: ${value}`));
    
    // CASE2 : sequential requests
    getName().concat(getDocument())
        .bufferCount(2)
        .map(values => `${values[0]}-${values[1]}`)
        .subscribe(value => console.log(`sequential: ${value}`));
    
    0 讨论(0)
提交回复
热议问题