3

I'm trying to clean up my Ionic app and move redundant functions into a provider. Below is an example of what most of my API functions look like (except I've stripped out a lot of irrelevant stuff for brevity) and they are working perfectly.

getDataFromApi() {
    ....
    Promise.all([
        ...
    ]).then((result) => {
        let headers = new Headers();
        ...
        let body = new FormData();
        ...
        this.http.post(url, body, headers)
            .map(res => res.json())
            .subscribe(data => {
                if (data == '{}') {
                    this.mydata = [];
                } else {
                    this.mydata = data;
                }
            }, error => { });
    });
}

So what I've done is move the function into the provider and changed it like so

getDataFromApi() {
    ....
    Promise.all([
        ...
    ]).then((result) => {
        let headers = new Headers();
        ...
        let body = new FormData();
        ...
        return this.http.post(url, body, headers));
    });
}

And then inside the page's constructor I'm calling it like this

this.service.getDataFromApi()
    .map(res => res.json())
    .subscribe(data => {
        if (data == '{}') {
            this.mydata = [];
        } else {
            this.mydata = data;
        }
    }, error => { });

Obviously that's not working. I've been pouring over SO posts for 2 days now and can't get other people's examples and answers to work properly either. I've tried mapping in the provider and subscribing when calling it in the page, but no matter what I try just keep receiving errors. I know this function is returning data because I can see it before moving it into the provider.

What am I doing wrong?

3
  • You must return your promise.all :) getDataFromApi() { .... return Promise.all([ ... ]) Commented Sep 28, 2018 at 15:28
  • Returning from a callback is not the same as returning from the function that created it. Commented Sep 28, 2018 at 15:31
  • Can you give us a clue what the "Promise.all" bit is doing? In general, you'd want to deal entirely with observables rather than changing back and forth - but without knowing what you're doing in those promises, it's hard to know... Commented Sep 28, 2018 at 16:14

2 Answers 2

2

I think the most important thing is to be consistent; you can do all that using only promises or using only observables (instead of mixing both).

Please take a look at this stackblitz demo where you can see how to make a similar HTTP request by using only observables and the exact same request using only promises.


Using observables

You can use switchMap (or any other flattening operator) together with fromPromise to handle all that in the same method from your provider:

// Angular
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

// RxJS 
// Please notice that this demo uses the 5.5.2 version
import { Observable } from 'rxjs/Observable';
import { map } from 'rxjs/operators/map';
import { tap } from 'rxjs/operators/tap';
import { switchMap } from 'rxjs/operators/switchMap';

// ...

// Get a list of 5 users using observables
public getDataUsingObservables(): Observable<any> {
  const promises = Promise.all([
    this.somePromise('getDataUsingObservables', 1),
    this.somePromise('getDataUsingObservables', 2)
  ]);

  return fromPromise(promises)
    .pipe(
      switchMap(results => {
        console.log(`[getDataUsingObservables]: Both promises are finished`);
        const url = `https://randomuser.me/api/?results=5`;

        return this.http.get<any>(url);
      }),
      tap(res => {
        console.log(`[getDataUsingObservables]: The http request is finished`);
      })
  );
}

// Return a promise with the number sent as parameter 
private somePromise(callerMethod: string, aNumber: number): Promise<number> {
  console.log(`[${callerMethod}]: About to create a promise with the number ${aNumber}`);
  return Promise.resolve(aNumber);
}

And then you'd use it like this:

this.dataService.getDataUsingObservables().subscribe(
  response => {
    this.responseUsingObservables = response;
  }, 
  error => {
    // Handle the error...
    alert(error);
  });

Using promises

If you want to use promises, you can use the toPromise() operator to transform the observable into a promise:

// Angular
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

// RxJS 
// Please notice that this demo uses the 5.5.2 version
import { tap } from 'rxjs/operators/tap';

// ...

// Get a list of 5 users using promises
public getDataUsingPromises(): Promise<any> {
  const promises = Promise.all([
      this.somePromise('getDataUsingPromises', 1),
      this.somePromise('getDataUsingPromises', 2)
  ]);

  return promises
    .then(results => {
      console.log(`[getDataUsingPromises]: Both promises are finished`);
      const url = `https://randomuser.me/api/?results=5`;

      return this.http.get<any>(url)
        .pipe(
          tap(res => {
            console.log(`[getDataUsingPromises]: The http request is finished`);
          })
        )
        .toPromise();
    });
}

// Return a promise with the number sent as parameter 
private somePromise(callerMethod: string, aNumber: number): Promise<number> {
  console.log(`[${callerMethod}]: About to create a promise with the number ${aNumber}`);
  return Promise.resolve(aNumber);
}

And then you'd use it like this:

this.dataService.getDataUsingPromises()
  .then(response => {
    this.responseUsingPromises = response;
  })
  .catch(error => {
    // Handle the error...
    alert(error);
  });
Sign up to request clarification or add additional context in comments.

6 Comments

I can't get anything to return when using it. Even if I do this.service.someNewMethodAsPromise().then(data => { console.log('here') } I get nothing; no print in the console.
Could you please add some console.log(...) every one or two lines of code to see what part is executed?
Running this.service.someNewMethodAsPromise().then(data => { console.log('here'); }) outputs nothing, but this.service.someNewMethodAsPromise().then(console.log('here')) outputs here
@timgavin Hmm, that's strange. Could you please take a look at this stackblitz demo that I've created, where I get data using both only promises and only observables. In that example I've used the code from my answer, and added a few tap pipes just to print a message in the console (you can remove them if you want).
This isn't working for me, probably because I'm using an older version of Angular and can't upgrade right now. Thank you though!
|
0

Disclamer: There might/should be a way to implement the exact same concept in observables, but I'm not too familiar with them, so maybe someone can elaborate.

Your function should return a promise, which means that you would do

getDataFromApi() {
    ....
    return Promise.all([
        ...
    ]);
}

And then implement it's logic in the then part in your caller function. Now that's not much fun since you will replicate your code, which you're trying to eliminate.

For that, your function should still return a promise, that resolves after your desired return statement like so :

getDataFromApi() {
  ....
  new Promise((resolve, reject) => {
    Promise.all([
      ...
    ]).then(result => {
      const headers = new Headers();
      ...
      const body = new FormData();
      ...
      // This is equavilent to your desired return
      resolve(this.http.post(url, body, headers));
    });
  });
}

And then implement the POST subscription in the caller then block like so :

this.service.getDataFromApi()
  .then(observ => {
    observ.subscribe(result => {
    // Use your response here
    });
  });

You can even use async functions, which would be much easier in the code, just instead of wrapping the function in a new promise, you can just await on Promise.all, and then the return that will follow will be translated to a promise resolve for you.

Edit: Even better, if every time you just subscribe on JSON data, and would like it returned, the concept can be generalized to make the code in the caller more concise:

this.service.getDataFromApi().then(finalData => {
  // This is the final returned data ...
}, (err) => {
//allows error to be handled
});

This can be achieved by resolving the promise in the providers method in the subscription block like so:

getDataFromApi() {
  return new Promise((resolve, reject) => {
    Promise.all([...]).then(result => {
      const headers = new Headers();
      const body = new FormData();
      this.http
        .post(url, body, headers)
        .map(res => res.json())
        .subscribe(data => {
          if (data == '{}') {
            reject([]);
          } else {
            resolve(data);
          }
        });
    });  
  })
}

12 Comments

@StephenRomero Unfortunately that didn't work; same error. However, I did notice another error being spit out by Ionic: resolve is not defined, so i removed resolve(data) and replaced it with return data, but still receiving the uncaught (in promise) error.
I must say that mixing promises and observables is a bad practice since you can do exactly the same using only promises or only observables (using observables is actually the recommended way in Angular since the http client and some other core features have been implemented based on observables).
@sebaferreras you're absolutely right! that's why I left that disclaimer in the beginning of the post since I don't know much about observables. Just upvoted your answer :)
@StephenRomero The same thing can be applied to observables as well when they are used for API requests (we all assume the API is going to return something even if it's an error). Both promises and observables can be used for API requests but observables are more powerful than promises (for example, you can cancel ongoing requests using observables, but you can't do that with promises). I prefer to use observables in order to be consistent with the way angular deals with async code but, of course, you can use only promises for that as well and the result should be similar.
@sebaferreras Thanks for that, I will be doing alot more research on Observables.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.