NickD
NickD

Reputation: 69

Wait till all Observables are completed

I have few Observables like this one in my code.

this.server.doRequest().subscribe(response => console.log(response)
      error => console.log(error),
      () => {
        console.log('completed');
      });

There could be any number of these Observables, so I need to write a function that checks if each Observable is done otherwise waits till each is finished.

I'm assuming I can create an array push every new Observable there and when it's completed remove it by index. But is it good solution?

Where I want to use it. For example I have a page where user upload photos any amount asynchronously and then he press Finish button. Once he pressed Finish button I need to wait till ALL dynamically created Observables are completed.

Upvotes: 3

Views: 8805

Answers (2)

bryan60
bryan60

Reputation: 29355

you should use higher order observables for this, your exact use case will dictate the exact operator, but forkJoin seems a good candidate:

forkJoin(
  this.server.doRequest1(),
  this.server.doRequest2(),
  this.server.doRequest3(),
  this.server.doRequest4()
).subscribe(vals => console.log('all values', vals));

forkJoin won't emit till all innter observables have completed. making it the operator of choice for waiting for multiple observables to complete. You can also feed it an array of observables. There are multiple other operators that may fulfill your case too, such as concat, merge, combineLatest or a few others.

edit based on more details:

in the use case described in your update, you'll still want to use a higher order observable, but forkjoin is not what you want. you'll want to use a local subject to accomplish the goal as wanting to kick off each observable as it is selected and waiting for them all to be done complicates things a little (but not too much):

suppose you had a template like:

<button (click)="addPhoto()">Add Photo</button>

<button (click)="finish()">Finish</button>

where the add photo button gets the users photo and all that, and finish is your completion, you could have a component like this:

private addPhoto$ = new Subject();

constructor() {
  this.addPhoto$.pipe(
    mergeMap(() => this.uploadPhoto()),
  ).subscribe(
    (resp) => console.log('resp', resp),
    (err) => console.log('err', err),
    () => console.log('complete')
  );
}

private uploadPhoto() {
  // stub to simulate upload
  return timer(3000);
}

addPhoto() {
  this.addPhoto$.next();
}

finish() {
  this.addPhoto$.complete();
}

if you run this code, you'll see that the photo adds will emit in the subscribe handler as they complete, but complete will only fire once all the photo uploads have completed and the user has clicked finish.

here is a stackblitz demonstrating the functionality:

https://stackblitz.com/edit/angular-bsn6pz

Upvotes: 5

tgralex
tgralex

Reputation: 814

I'd create a dictionary (in javascript that would be a JSON with observable names as boolean properties) where you push each observable on "create" and a method which should execute on completion of each observable, which will iterate through that dictionary and if all completed do something. That will ensure parallelism and final execution after all completed.

var requests = {
    doRequest1: false,
    doRequest2: false,
    doRequest3: false
};

var checkIfCAllCompleted = name => {
    requests[name] = true;
    for (var property in requests) {
        if (object.hasOwnProperty(property)) {
            if (!property) {
                return;
            }
        }
    }
    // all properties are true - do something here
    console.log("here");
}
this.server.doRequest1().then(() => checkIfCAllCompleted("doRequest1"));
this.server.doRequest2().then(() => checkIfCAllCompleted("doRequest2"));
this.server.doRequest3().then(() => checkIfCAllCompleted("doRequest3"));

Upvotes: 0

Related Questions