Dan Chase
Dan Chase

Reputation: 1042

Need help getting Promise.all to work in Angular

Up until now, I've been creating functions that return directly from HTTP get, and then calling .subscribe on the return value. However, recently I've learned about Promise.all, and would like to know how I can use it to wait for both HTTP gets to be completed.

Example of how I'm currently doing it:

function getCustomerList() {
 return this.http.get("http://myapi.com/getCustomerList");
}

function otherFunction() {
  this.getCustomerList().subscribe() {
    data => {}, err => {}, () => {}
  }
}

This seems to work OK, but I keep wondering if I could do something like this instead:

function getCustomerList() {
  return this.http.get("http://myapi.com/getCustomerList").subscribe( data => {}, err => {}, () => {});
}

function getVendorList() {
  return this.http.get("http://myapi.com/getVendorList").subscribe( data => {}, err => {}, () => {});
}

function getAllInfo() {
  var p1 = getCustomerList();
  var p2 = getVendorList();

  Promise.All([p1, p2]);
  console.log("This should not run until both have returned");
}

But it always runs right away. So I tried to use .toPromise() in the get() functions with .then(), and it did the same thing. The only thing I haven't tried is maybe putting a .toPromise() on the outer function.

Upvotes: 3

Views: 3991

Answers (4)

inorganik
inorganik

Reputation: 25525

The reason they ran right away is because you returned the subscription. You could do this:

function getCustomerList() {
  return this.http.get("http://myapi.com/getCustomerList").toPromise();
}

function getVendorList() {
  return this.http.get("http://myapi.com/getVendorList").toPromise();
}

function getAllInfo() {
  const p1 = getCustomerList();
  const p2 = getVendorList();

  Promise.all([p1, p2]).then(values => {
    console.log("This should not run until both have returned", values);
  });
}

But even better, you could use forkJoin:

import { forkJoin } from 'rxjs';

// component declaration and constructor omitted

customerList$ = this.http.get("http://myapi.com/getCustomerList");
vendorList$ = this.http.get("http://myapi.com/getVendorList");

getAll() {
  forkJoin(this.customerList$, this.vendorList$).subscribe(result => {
    console.log(result);
    // [{ customerList: ... }, {vendorList: ... }];
  });
}

Upvotes: 5

Suren Srapyan
Suren Srapyan

Reputation: 68655

You are using not Promises in the Promise.all, but Observables. You need to promisify them using .toPromise(). Then you need to continue your logic after fulfillment of the promise.

function getCustomerList() {
  return this.http.get("http://myapi.com/getCustomerList").toPromise();
}

function getVendorList() {
  return this.http.get("http://myapi.com/getVendorList").toPromise();
}


function getAllInfo() {
  var p1 = getCustomerList();
  var p2 = getVendorList();

  Promise.All([p1, p2]).then(_ => console.log("This should not run until both have returned"));  
}

As @jonrsharpe mentioned in the comments, it is not logically not wrap observable into promise for getting parallel work. You can use various of RxJS operators like combineLatest or zip to make wait for two observable to finish their work.

Upvotes: 2

GregL
GregL

Reputation: 38111

Observables are lazier than promises

The first thing you have to realize is that observables are lazy, meaning they don't do anything (in this case, make the request) until you subscribe to them.

Promises, on the other hand, are eager, meaning they start doing work straight away, whether you do anything with the promise or not.

So yes, by using promises, you will make those requests straight away.

You could use Promise.all() if you want to

Here is how you would be best to structure your code to do what you want:

function getCustomerList(): Observable<CustomerList> {
  return this.http.get("http://myapi.com/getCustomerList");
}

function getVendorList(): Observable<VendorList> {
  return this.http.get("http://myapi.com/getVendorList");
}

async function getAllInfo() {
  var p1 = getCustomerList();
  var p2 = getVendorList();

  await Promise.all([
    p1.toPromise(),
    p2.toPromise(),
  ]);
  console.log("This should not run until both have returned");
}

By using async/await and converting to promises just for the call to Promise.all(), you will achieve exactly what you want to do.

But there is a better way without using promises

Since the Http service returns observables, which are kind of like super-promises, you can just use an observable operator to combine the two.

The only downside is you won't be able to use async/await with it.

Simply use combineLatest() to combine the two return values as an array, just like with Promise.all().

function getCustomerList() {
  return this.http.get("http://myapi.com/getCustomerList");
}

function getVendorList() {
  return this.http.get("http://myapi.com/getVendorList");
}

function getAllInfo() {
  var p1 = getCustomerList();
  var p2 = getVendorList();

  combineLatest(p1, p2)
    .subscribe(([customerList, vendorList]) => {
      console.log("This should not run until both have returned");
    });
}

Or you could return the result of combineLatest(), rather than subscribing to it directly.

Upvotes: 2

Matt
Matt

Reputation: 2290

when using .toPromise() you no longer have to subscribe

return this.http.get("http://myapi.com/getCustomerList").toPromise();

also your log won't wait for the promises to complete. You'll either have to log in a .then() function.

Promise.All([p1, p2])
  .then(t => console.log("This should not run until both have returned"));

or mark getAllInfo as async and await the Promise.all

await Promise.All([p1, p2]);
console.log("This should not run until both have returned");

I'd recommend looking more into async/await as well and not so much into observables. Just always convert them using .toPromise()

Upvotes: 2

Related Questions