Async operation before sending HTTP request

Hi there.
I’m trying to send a standard GET request to an API that returns some categories as json data. The server is secured with basic auth, so I have to manually create the request options. This takes some time, so I chose to implement an Observable that returns the request options and completes after that.
However, when executing I get

Uncaught (in promise): TypeError: Cannot read property ‘subscribe’ of undefined

Call to Server in categoryService:

  getCategories() : any {
    console.log("Getting categories from: " + this.serviceUrl);

    // creating the request
    let url = this.serviceUrl + "category/appCategories";

    // getting request options
    this.userService.getRequestOptionsForLoggedInUser().subscribe(opt => {
      // sending request to server
      // returning observable to subscribe to
      return this.http.get(url, opt)
        .map(res => res.json());
    });
  }

Calling the function:

  this.categoryService.getCategories().subscribe(categories => {
        console.log("Fetched categories from server: ", categories);
  }

My guess: getCategories() is completed before the request is sent, therefore it does not return an Observable to subscribe to.
Any ideas on how to fix this? Should I use Promises instead of Observables?

Thanks in advance!

Can you show what this function looks like? The problem is that getCategories itself doesn’t actually return anything, but I feel like you’re doing some unnecessary stuff, so before I provide sample code I’d like to see the code for the getRequestOptionsForLoggedInUser :slight_smile:

Sure:

getRequestOptionsForLoggedInUser(): Observable<RequestOptions> {
    return Observable.create(observer => {
      let obs = observer;

      this.getCredentials().subscribe(user => {

        // this just encodes the user data to base64
        let auth = this.baseEncodeUserCredentials(user);

        // building the header
        let opt: RequestOptions;
        let myHeaders: Headers = new Headers

        myHeaders.append('Authorization', 'Basic ' + auth);

        opt = new RequestOptions({ headers: myHeaders });

        obs.next(opt);
        obs.complete();
      });
    });
  }

  getCredentials(): Observable<User> {
    return Observable.create(observer => {
      let obs = observer;
      this.storage.get('username').then(username => {
        this.storage.get('password').then(password => {
          let user: User = new User(username, password)
          obs.next(user);
          obs.complete();
        });
      });
    });
  }

User Data is stored locally.
It worked fine before I changed getRequestOptionsForLoggedInUser to return an observable. Before that it returned RequestOptions with hard coded user data and not from storage. Since I get the name and password from storage now, I had to change it to wait until the data is there, otherwise it would create RequestOptions with an empty authentification.

Yeah you should use Promises for some of these, here’s my approach. It’s a bit … clumsy, so to say, because you’re wanting to return the Observable from the HTTP call within a Promise (or what was previously an Observable). I couldn’t find a way for it to simply return the Observable, I might be too tired…

Bear in mind I didn’t test this! Just quickly wrote it up.

getRequestOptionsForLoggedInUser(): Promise<RequestOptions> {
	return new Promise((resolve, reject) => {
		this.getCredentials().then(
			user => {
				// this just encodes the user data to base64
				let auth = this.baseEncodeUserCredentials(user);
				
				// building the header
				let opt: RequestOptions;
				let myHeaders: Headers = new Headers
				
				myHeaders.append('Authorization', 'Basic ' + auth);
				
				resolve(new RequestOptions({ headers: myHeaders }));
			},
			err => {
				reject(err);
			}
		);
	});
}
getCredentials(): Promise<User> {
	return new Promise((resolve, reject) => {
		Promise.all([this.storage.get("username"), this.storage.get("password")]).then(
			data => {
				// username = data[0]
				// password = data[1]
				resolve(new User(data[0], data[1]));
			},
			err => {
				reject(err);
			}
		);
	});
}
getCategories(): Promise<any> {
	return new Promise((resolve, reject) => {
		console.log("Getting categories from: " + this.serviceUrl);
		
		// creating the request
		let url = this.serviceUrl + "category/appCategories";
		
		// getting request options
		this.userService.getRequestOptionsForLoggedInUser().then(
			opt => {
				// sending request to server
				// returning observable to subscribe to
				resolve(this.http.get(url, opt).map(res => res.json()));
			},
			err => {
				reject(err);
			}
		);
	});
}
this.categoryService.getCategories().then(
	obs => {
		obs.subscribe(
			data => {
				// Handle http data
			},
			err => {
				// Handle http error
			}
		);
	},
	err => {
		// Handle error
	}
);

btw. it is not necessary to double wrap already promises in new Promise. Simply return the success value or use “throw” to raise a custom exception, which leads to reject.

const promiseFunction = () => {
  return new Promise((resolve, reject) => {
    resolve(true);
  });
}

promiseFunction().then(() => { throw "Error" })
                 .then(() => {}, (err) => { console.log(err) });

the same with Promise.all --> it returns a promise, no need to wrap everything again in a promise :wink:

e.g.

getCategories(): Promise<any> {
    console.log("Getting categories from: " + this.serviceUrl);
    
    // creating the request
    let url = this.serviceUrl + "category/appCategories";
    
    // getting request options
    return this.userService.getRequestOptionsForLoggedInUser()
                    .then(opt => {
                        // sending request to server
                        // returning observable to subscribe to
                        return this.http.get(url, opt).map(res => res.json());
                    });
}
1 Like

Thank you! I’m trying to change my code to use Promises now, but that might take a while :wink:

When I change it to the code bengtler just suggested I get a typescript error:

A function whose declared type is neither ‘void’ nor ‘any’ must return a value.

Which I personally don’t understand, since the function is returning the http response…

It’s because getCategories() isn’t returning anything, the return is for the function in the promise, .then(opt => { ... }.

changed it. sorry for the confusion … i hacked it together on the fly :wink:

Works like a charm now! Thank you both! :slight_smile: