Getting Observable.fromPromise is not a function and Converting circular structure to JSON error


#1

This question is really about Angular 2 or TypeScript Object Oriented Programming that I am trying to implement in my Ionic 2 app. Would appreciate if somebody can please help me resolve with an issue I am facing right now. I tried to look around by Googl-ing it but didn’t get anything helpful.

I am getting the following error and not sure how to troubleshoot it:

WEBPACK_IMPORTED_MODULE_2_rxjs_Observable.Observable.fromPromise is not a function

Basically, my app connects with cloud backend to call different REST APIs. The REST API returns a Promise which I am trying to convert into an Observable. I have created a base class that has the function, to make this REST call, which returns an Observable.

Please allow me to explain:

So, in the app I have different services/providers for each of the functionalities, similar to the functionality’s individual pages (html, scss and ts files). For example:

  • home-page.ts -> home-service.ts -> REST call in Cloud

  • phone-detail-page.ts -> phone-service.ts -> REST call in Cloud

As soon as the user has successfully logged in, I set the ROOT page as HomePage (home-page.ts) in which, in the ionViewDidLoad() function I am calling a function from HomeService (home-service.ts) which in-turn is making a REST API call to get the home page data.

Another thing is, due to the cordova-javascript library this cloud service provides, I need to make the REST API call using a specific function on the cloud-connection object I get. Let’s say the function name is invokeMethod(URL, methodType). For example:

cloudConn.invlokeMethod('/home/data', {method = GET});

Now, since I have multiple services/providers that are going to call Cloud based REST API, I have created a base class for my services/providers. And, as soon as the cloud-connection is established successfully (of course, only upon login) I set the base class cloud-connection object with the one I get and then use it for all further cloud based REST calls.

Since I am dealing with Observable in all of my code except for this Promise based REST call, I am converting this Promise into an Observable in the base class and that’s where I am getting the error mentioned above.

Following is a sample of my base-class and a service/provider class that is extending the base class and calling the base-class function to make a REST API call.

base-service.ts

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';

@Injectable()
export abstract class MyBaseService {
  private static cloudConn : any;

  constructor(public http: Http) {
  }

  protected setCloudConn(conn) {
    MyBaseService.cloudConn = conn;
  }

  public invokeRestMethod(url: string, methodType: string): Observable<any> {
    return Observable.fromPromise(MyBaseService.cloudConn.invokeMethod(url, { method: methodType }));
  }
}

home-service.ts

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Http, Response } from '@angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';

import { MyBaseService } from './base-service';

@Injectable()
export class HomeService extends MyBaseService {
  constructor(public http: Http) {
    super(http);
  }

  getHomeData(): Observable<DataObj[]> {
        return super.invokeRestMethod(myURL, 'GET')
          .map((res:Response) => res.json())
          .catch((error:any) => Observable.throw(error.json().error || 'Server error'));
      }
  }
}

home-page.ts

import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';

import { HomeService } from '../../providers/providers';

@Component({
  selector: 'page-home',
  templateUrl: 'home.page.html'
})
export class HomePage {
  data: any[];

  constructor(public navCtrl: NavController, public navParams: NavParams,
              private homeService: HomeService) {
    console.log('In HomePage constructor');

  }

  ionViewDidLoad() {
    console.log('ionViewDidLoad HomePage ');

     this.homeService.getHomeData()
      .subscribe(
        (results) => {
            this.data= results;
        },
        (error) => {
          this.showError(error);
        }
      );
  }
}

Hope I have provided all the required information. I would highly appreciate any help/pointers/suggestions to resolve this issue.

EDIT : Converting circular structure to JSON Error after getting past ‘fromPromise’ related error.

I thought continuing here since I have all the code already shown here.

After importing ‘fromPromise’ as pointed out by @AaronSterling, I was able to move forward. However, I am getting an error which I believe is in my HomeService (home-service.ts) following code:

  getHomeData(): Observable<DataObj[]> {
        return super.invokeRestMethod(myURL, 'GET')
          .map((res:Response) => res.json())
          .catch((error:any) => Observable.throw(error.json().error || 'Server error'));
      }
  }

at:

.catch((error:any) => Observable.throw(error.json().error || ‘Server error’));

Since, it’s directly going into “catch” here I am guessing something in my BaseService is not correct but not able to figure out what it can be as it’s a single line of code.

Any help will be highly appreciated.


#2

Where is import 'rxjs/add/observable/fromPromise';?


#3

Instead of calling toPromise() as a class method, try calling it on an Observable object, such as MyBaseService.cloudConn.invokeMethod(url, { method: methodType }).toPromise(). I know that syntax works.


#4

@AaronSterling - Thanks for your reply, very much appreciated! This proves, half knowledge is always dangerous. :frowning: Don’t know why I could not think about that.
Surprisingly, none of the first page search result on Google talked about it.

Thanks again!


#5

Hi @rapropos - I am actually doing the other way round. I am converting a Promise into Observable by using fromPromise(). My apologies, if my question was not clear. I know I added a lot of explanation in my question.
Thanks for the quick reply though. :slight_smile:


#6

I’ve found it useful to look at the add folder of the rxjs node-modules. Helps me see at a glance how to import operators etc.


#7

@AaronSterling, @rapropos - In continuation to my first question, the same code is giving another error that says Converting circular structure to JSON.

While going through different forums, people are talking about JSON.stringify() or zone.js adding another parameter to the returned json object or something similar. But, honestly, I am able to relate all that information with what I am doing.

Any suggestions or pointers on what I should be doing?

Thank you!


#8

What are these json objects you are loading? Your own creation? Provided by a third party?


#9

Hi @AaronSterling, they are my own creation. It’s actually the output I am expecting from my base-class function
MyBaseService.cloudConn.invokeMethod(url, { method: methodType }).

The cloudConn.invokeMethod() function is not mine and it always returns a Promise. Since I want to deal with Observable in my application code, I am converting this Promise into Observable (using Observable.fromPromise()) and deal with the returned value(s) in my individual services that inherited from my base-service class.

If I keep it is as Promise and use .then on it, I do get the expected output. So, I am definitely not reading the returned value/output after I convert it into an Observable. And I am not able to think of a way to do it.

I am expecting an object in the following format (sample):

[ {
  "id" : 1,
  "name" : "Sample 1",
  "desc" : "Sample Desc 1",
  .
  .
  .
}, {
  "id" : 2,
  "name" : "Sample 2",
  "desc" : "Sample Desc 2",
  .
  .
  .
} ]

I do get the output like above when I run the URL in a browser or in Postman tool. So the URL is good.

Any idea on how I should be reading an output of Promise after I convert it into an Observable?

Thank you.


#10

The error code is for faulty JSON. Have you run it through a JSON validator?


#11

@AaronSterling, I am able to get the same JSON if I do not convert to Observable and instead use the .then() on the Promise. So I am guessing the JSON should be good.

But since you asked, I checked and it’s a valid JSON.


#12

What’s the line that causes the error?


#13

I think the conversion from Promise to Observable in MyBaseService class is good. It’s the way I am trying to read from the returned Observable using .map() in my home-service.ts (I have the class shown in my original post), that is somewhere in:

getHomeData(): Observable<DataObj[]> {
        return super.invokeRestMethod(myURL, 'GET')
          .map((res:Response) => res.json())
          .catch((error:any) => Observable.throw(error.json().error || 'Server error'));
      }
  }

Or, I am completely wrong perhaps. Maybe the main problem is in the way I am converting and returning in my base-class which I am doing as:

  public invokeRestMethod(url: string, methodType: string): Observable<any> {
    return Observable.fromPromise(MyBaseService.cloudConn.invokeMethod(url, { method: methodType }));
  }

But I am doing this by following the fromPromise’s API doc of rxJS.


#14

Can you share any details about this nebulous service that is generating the promise to start with? Is its source easily viewable somewhere like GitHub?


#15

I don’t know if this is the specific problem, but as a general principle, if you’re using the word “any” in Typescript, it’s 99% likely you’re doing something wrong. So that’s the first thing I’d investigate. Unless you can provide a strict return type to invokeRestMethod, you’ve got a runtime error waiting to happen. At a miminum, use of any means that you don’t understand what’s going on as well as you should.

I would debug with .do. As in

return super.invokeRestMethod(myURL, 'GET')
              .do(x => console.log('before json',x)
              .map(same as before)

and see what you’re getting back before you enter json land.


#16

Hi @rapropos, the service that is generating the Promise is not mine. It’s an API provided by the library I am using.

I was trying avoid it because I thought the discussion might dwell into different way. But I will put it here.

Following is the scenario:

  1. Basically, I have some REST APIs written in Java that I have deployed on Microsoft Azure cloud’s API App.

  2. Then I have something called Mobile App backend that Azure Cloud provides to implement the backend part of your mobile app. This Azure Mobile App Backend provides features like Push Notifications, Offline Sync, etc. This Mobile App Backend is implemented in Node.js.

  3. Now from my Ionic 2 mobile app, in order to call a Java REST API I need to use an Azure JS (cordova) library that is provided by Microsoft.

    a. Using this library, I need to first connect with my Mobile App Backend. This gives me an object (cloudConn in my code above).

    b. This object (in bullet #a above) provides a function called invokeMethod (or invokeApi) but they have implemented it to return a Promise. And this is what I am trying to convert into an Observable.

Note: When I just use the Promise (i.e. don’t convert into Observable) I can get the expected JSON in .then() using the .result method/property as below in my home-service.ts:

       super.invokeRestMethod(myURL, 'GET').then(
          (res) => {
            let data= res.result; 
          },
          (err) => {
            console.log('error:' + err);
          }
        );

Hope this helps.


#17

@AaronSterling - that came to my mind too but I am not sure what type of Object the Promise is having so I used ‘any’. Will give it a try the way you said.

Thanks for patiently replying to my questions and help requests, I really appreciate it very much.


#18

The entire point of using REST is that it’s all done via vanilla HTTP. There is no need for getting locked into vendor-specific client libraries like this beast you seem to be leashed to. There must be a way to host a real REST service on Azure, that you can access with Angular’s Http and bypass all this nonsense.

However, if you don’t want to go down that path, one simple and probably weird-sounding thing to try is to wrap every “Promise” that you get from this Azure library inside Promise.resolve() before doing anything else to it. So:

Observable.fromPromise(Promise.resolve(cloudConn.invokeMethod()));

The logic here is that all Promises are not really created equal, which is a subset of a larger problem that JavaScript’s “object-oriented” features are really a mirage. It’s tempting, especially when coming from a Java background, to try to design object hierarchies like you would in Java in JavaScript. After a few years of frustration, I decided to completely give up on that, so in fact I would ditch your entire inheritance structure entirely in favor of a solution using composition and DI, but you didn’t ask my opinion about that.

In any event, if your problems are stemming from the fact that this Azure library is feeding you things that sort of look like the Promises that Angular is wanting but don’t really do it properly, wrapping it in Promise.resolve() should protect you from that.


#19

This matches 100% my experience too. I also knew Java before Javascript, and in fact I thought Javascript was a second-tier language for years. It wasn’t until I got serious about the idea of hybrid programming for the browser that I took Javascript seriously. And when I saw your OP, I said to myself, “Using super, I wonder if that’s the problem.” But I didn’t bring it up because it seemed too opinion-y. I figured I should mention it now though, so you know it’s not just some crazy idea of @rapropos.

Also, there’s programming language justification for DI, instead of polymorphism. It allows pages to know virtually nothing about structure, and only process based on inputs. So pages become pure functions.


#20

@rapropos - if I could I would have definitely used Angular 2’s http but due to some environment setup restrictions, we are not able to.

I guess, I have spent enough time now on this conversion thing. I will try what you suggested with Promise.resolve() as one last resort otherwise I will start using Promises.

TypeScript only appears object-oriented anyways, ultimately it boils down to JavaScript only. But this OO facade has what made it more accessible, I think, for people like me. :slight_smile:
Coming from Java background I always found JavaScript very very cumbersome and therefore complicated which in turn kept me way away from it. :smiley:

Thanks for your kind help and keeping your patience. :+1: Appreciated!