Returning Data from Javascript Promise [duplicate] - javascript

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 5 years ago.
This is done in angular 2 and typescript.
I am trying to return data from the server and pass it back to a component in angular.
getUsers(): Promise<User[]> {
return this.http.get(this.baseUserUrl + 'GetUsers')
.toPromise()
//.then(resp => resp.json() as User[])
.then(this.returnData)
.catch(this.handleError);
}
private handleError(error: any): Promise<any> {
console.error('An error occurred', error); // for demo purposes only
return Promise.reject(error.message || error);
}
private returnData(data: any): User[] {
var rtn = data.json() as User[];
return rtn;
}
Data is returned from the server fine and is casted to User[] fine also.
However, its not returned to the javascript method.
ngOnInit(): void {
this.userService.getUsers().then(users => this.users = users);
}
My this.users is just a bunch of promise related stuff and not the users array.

Explanation: You already call .then in getUsers() so calling it twice wil not have the data it's about chaining a promise, so for your code you can set returnData to a Promise too and can chain it like you do.
private returnData(data: any): Promise<User[]> {
var rtn = data.json() as User[];
return Promise.resolve(rtn);
}
now you can use:
this.userService.getUsers().then(users => this.users = users);
But, but the simplest way is using the observable map operator :
getUsers(): Promise<User[]> {
return this.http.get(this.baseUserUrl + 'GetUsers')
.map(users => this.returnData(users))
.toPromise()
}
and in ngOnInit:
ngOnInit(): void {
this.userService.getUsers().then(users => {
this.users = users
});
}
Or just observable:
getUsers(): Observable<User[]> {
return this.http.get(this.baseUserUrl + 'GetUsers')
.map(users => this.returnData(users))
}
and
ngOnInit(): void {
this.userService.getUsers().subscribe(users => {
this.users = users
});

The promise returns immediately, so this won't work.
The success and error events need to be handled up on the model level so you can do what you want with the data (not the service's responsibility). The service just gets data and sends it along to the callbacks.
Something like this:
loadUsers(successCallback, errorCallback): Promise<User[]> {
return this.http.get(this.baseUserUrl + 'GetUsers')
.toPromise()
.then(successCallback)
.catch(errorCallback);
}
// MOVE METHODS TO MODEL CODE
var self = this;
self.users = [];
private handleError(error: any): Promise<any> {
console.error('An error occurred', error); // for demo purposes only
return Promise.reject(error.message || error);
}
private loadData(data: any): User[] {
self.users = data.json() as User[];
}
ngOnInit(): void {
this.userService.loadUsers(loadData, handleError);
}

Related

Call to backend works properly inside service, but not outside service

I'm trying to make a call to my backed, and it returns data just fine inside of my service, but when I try to use that service in a different module, it either logs undefined or Observable. Can anyone point me in a good direction? I've read about observables but I'm not 100% sure I'm getting them or if I even need one at this point. I was having problems with things on my page loading before I can tell them where to go which would throw an error.
Anyways here is the call to the backend, which logs out the game data as intended. This is inside my service.
async getAllGames() {
this.http.get(this.url).subscribe(gameData => {
console.log(gameData);
return gameData;
});
}
But when I call my service function from my other module, it returns undefined.
async getGames() {
const game = await this.games.getAllGames()
console.log(game);
_.each(game, (gameData) => {
this.gamesArray.push(gameData);
});
// this.loadCharts(this.gamesArray);
}
Don't use subscribe inside service. Just return an Observable from service then you can subscribe to that in any of your components you need.
Change your service code as follows.
getAllGames(): Observable<any> {
return this.http.get(this.url);
}
Then you can subscribe to that Observable any of your component as follows.
this.games.getAllGames()
.subscribe((res: any) => {
// TODO: do what you need with response data
});
PS
If you still need to subscribe inside service you should return a Promise.
getAllGames(): Promise<any> {
return new Promise((resolve, reject) => {
this.http.get(this.url).subscribe((res: any) => {
resolve(res);
}, (err: any) => {
reject(err);
})
});
}
Call the Promise from any of your components.
this.games.getAllGames()
.then(data => {
console.log(data);
})
.catch(err => console.error(err))
StackBlitz
I suggest such a solution, allowing you to get data in many components using the same service:
service:
class APIService {
private url: '...'
public getGames$;
constructor(private http: HttpClient) {
this.getGames$ = this.http.get(this.url);
}
}
component:
private games;
constructor(private api: APIService) {
this.service.getGames$.subscribe(games => {
this.games = games;
});
}

Return a Observable from a Subscription with RxJS

I currently have a service that do a HTTP request to an API to fetch data. There are some logic that I want to do to the observable from within the service, but I still want to also subscribe to the Observable in my Component so that I can return any errors to the Component and to the user.
Currently I do:
// service.ts
getData(): Observable<any> {
return this.http.get(url).pipe(catchError(this.handleError)
}
// component.ts
ngOnInit() {
this.service.getData().subscribe(res => {
// Do my logic that belong to the service.ts
// ...
// Do my logic that belongs in the component.ts
// ...
}, err => this.errors = err)
}
What I would like to do is to refactor this so that I handle the logic related to the subscription and the service.ts within the getData() method, and then return an Observable with the HTTP response and any errors to the Component so that I can continue doing things there.
What's the pattern to do this?
I feel like multiple of the patterns and solutions posted is "ugly" or does not follow the Observable pattern (Like doing callbacks).
The cleanest, most "RxJS"-like solution I came up with was to wrap the service method's return value in a second Observable.
So the following:
// service.ts
getData(): Observable<any> {
return new Observable(subscriber => {
this.http.get(url)
.pipe(catchError(this.handleError)
.subscribe(res => {
// Do my service.ts logic.
// ...
subscriber.next(res)
subscriber.complete()
}, err => subscriber.error(err))
})
}
// component.ts
ngOnInit() {
this.service.getData().subscribe(res => {
// Do my component logic.
// ...
}, err => this.errors = err)
}
Use map:
// service.ts:
import { catchError, map } from 'rxjs/operators';
getData(): Observable<any> {
return this.http.get(url).pipe(
map(res => {
/* Your processing here */
return res;
}),
catchError(this.handleError)
)
}
Try this way
service.ts
getData(): Observable<any> {
return this.http.get(url).map(res=> <any>(res['_body']));
}
component.ts
this.service.getData().subscribe(response=>{
var res1 = JSON.stringify(response);
var res2 = JSON.parse(res1);
var res3 = JSON.parse(res2);
}); //parse response based on your response type
Option 1
If you subscribe Observable in component then only component will have that subscription and it must be passed back to service.
Option 2
Use this pattern.
service.ts
getData(doer: Function) {
let subscriptions = Observable.of({ data: 'response', isError: false })// request
.catch(error => Observable.of({ data: error, isError: true })) //handle error
.do(data => doer(data))
.subscribe();
this.handleSubscription(subscriptions); //subscription handling in service
}
component.ts
ngOnInit() {
this.getData(response => {
if (response.isError) {
///
} else {
let data = response.data;
// Process
}
})
}
Be careful: All the answers are for <= Angular 4. In Angular 5, you don't need a map() anymore, so just leave that out. just return this.http.get() as it returns an Observable, where you can subscribe on.
Furthermore, be aware you have to import HttpClient instead of Http.
You can directly use "map" and "catch" function on Observable returned by http.get method.
import { catchError, map } from 'rxjs/operators';
getData(): Observable<any> {
return this.http.get(url)
.map(res => {
/* Your processing here */
return res;
})
.catch(this.handleError);
}
You can remove this, and use map. In subscribe error, you can get error event.
If you use HttpClient, just use get!

Fetch data once with Observables in Angular 2

I have a service, what is used several times from a lot of my Angular 2 components. It fetches customer data from a Web API and returns an Observable:
getCustomers() {
return this.http
.get(this.baseURI + this.url)
.map((r: Response) => {
let a = r.json() as Customer[];
return a;
});
}
I inject this service in my root component, and in every component that wants to access the customers I just subscribe to that Observable:
this.customerService.getCustomers().subscribe(v => this.items = v);
However, every component who subscribes to my Observable causes another execution of the HTTP-request. But to fetch the data only once is enough.
If I try share(), it does not solve my problem:
getCustomers() {
return this.http
.get(this.baseURI + this.url)
.map((r: Response) => {
let a = r.json() as Customer[];
return a;
}).share();
}
Still the same issue. Any proposals which operators I have to use to only fetch data once?
1) You can simply save downloaded data in your service:
export class CustomersService {
protected _customers: Array<Customer>;
constructor(public http: Http) {}
public getCustomers(): Observable<Array<Customer>> {
return new Observable(observer => {
if (this._customers) {
observer.next(this._customers);
return observer.complete();
}
this.http
.get(this.baseURI + this.url)
.map((r: Response) => (r.json() as Array<Customer>))
.subscribe((customers: Array<Customer>) => {
this._customers = customers;
observer.next(this.customers);
observer.complete();
});
});
}
}
2) Shorter approach taking refresh parameter:
export class CustomersService {
protected _customers: Array<Customer>;
constructor(public http: Http) {}
public getCustomers(refresh?: boolean): Observable<Array<Customer>> {
if (!refresh && this._customers) {
return Observable.of(this._customers);
}
return this.http
.get(this.baseURI + this.url)
.map((c: Response) => (c.json() as Array<Customer>))
.do((customers: Array<Customer>) => {
this._customers = customers;
});
});
}
}
3) Taking advantage of ReplaySubject:
export class CustomersService {
protected _customers$: ReplaySubject<Array<Customer>> = new ReplaySubject(1);
protected _customersInitialized: boolean;
constructor(public http: Http) {}
public getCustomers(refresh?: boolean): Observable<Array<Customer>> {
if (refresh || !this._customersInitialized) {
this._customersInitialized = true;
this.http
.get(this.baseURI + this.url)
.map((c: Response) => (c.json() as Array<Customer>))
.subscribe((customers: Array<Customer>) => {
this._customers$.next(customers);
});
}
return this._customers$.asObservable().skip(+refresh).distinctUntilChanged();
}
}
And then:
this.customersService.getCustomers()
.subscribe(customers => this.customers = customers);
You can also expose the always up-to-date customers field from SomeService for read only purposes (like displaying in the templates) this way:
public get customers(): ReadonlyArray<Customer> {
return this._customers;
}
I would create a parent container, fetch the data once, and pass it to child components using #Input.
Parent:
#Component({
selector: 'BarFooHttpCaller',
template: ´<child *ngIf="data.length > 0" [data]></child>´
})
class BarFooHttpCaller {
private data: any;
constructor(private foobar:Foobar) {
this.data = {};
}
ngOnInit() {
this.foobar.getCustomers().subscribe(() => {
console.log('httpdone')
});
this.foobar.dataStream.subscribe((data) => {
console.log('new data', data);
this.data = data;
})
}
}
Child:
import { Component, Input } from '#angular/core';
#Component({
selector: 'child',
template: ´<div>{{data}}</div>´
})
export class Child {
#Input() data: any;
}
If you want multiple children to subscribe to the same observable, but only execute the observable once you can do the following.
Note that this does adhere to the design of observables since we are executing the observable in the service layer (Observable.fromPromis(stream.toPromise()) when execution should be done from the component subscribing. View https://www.bennadel.com/blog/3184-creating-leaky-abstractions-with-rxjs-in-angular-2-1-1.htm for more.
//declare observable to listen to
private dataObservable: Observable<any>;
getData(slug: string): Observable<any> {
//If observable does not exist/is not running create a new one
if (!this.dataObservable) {
let stream = this.http.get(slug + "/api/Endpoint")
.map(this.extractData)
.finally(() => {
//Clear the observable now that it has been listened to
this.staffDataObservable = null;
});
//Executes the http request immediately
this.dataObservable = Observable.fromPromise(stream.toPromise());
}
return this.staffDataObservable;
}
the share operator give the possibility to use the same stream's result with multiple observers. It could be good but you generate a new observable stream each time you call getCustomers(), there is no point to call share() since you didn't subscribe multiple times to this stream.
If you wanna share the data with multiple observers but make only one http call you simply have to create a second stream, feed by the http one, containing the data. After that, all your components could subscribe to it.
The code could be something like that
#Injectable()
class FooBar {
public dataStream:Subject<any> = new Subject();
constructor(private http:Http) {}
public getCustomers() {
return this.http
.get(this.baseURI + this.url)
.map((response:Response) => response.json())
.map((data) => {
this.dataStream.next(data);
return data;
})
}
}
#Component({})
class BarFooHttpCaller {
constructor(private foobar:Foobar) {}
ngOnInit() {
this.foobar.getCustomers().subscribe(() => { console.log('http done') });
this.foobar.dataStream.subscribe((data) => {
console.log('new data', data);
})
}
}
#Component({})
class OtherBarFoo {
constructor(private foobar:Foobar) {}
ngOnInit() {
this.foobar.dataStream.subscribe((data) => {
console.log('new data', data);
})
}
}
No need for custom implementations. A pipe will do the trick:
getCustomers$(): Observable<Customer> {
return this.http
.get<Customer>(this.baseURI + this.url)
.pipe(shareReplay(1));
}
Couple of things I did here:
Add shareReplay(1) pipe, to make sure the request is only done once (only thing needed to answer the question)
Remove map and made the get call typed
Postfix method name with $ to indicate an Observable is returned

How can I return the Http response?

I'm wondering if following thing is possible to do:
I need to return the response from http GET request directly instead of returning Observable<Response> instance.
The example might clarify whole thing a bit:
#Injectable()
export class ExampleService {
constructor( #Inject(Http) protected http: Http) { }
static model: { [uri: string]: any } = {}
public get(uri: string): any {
if (typeof ExampleService.model[uri] === 'undefined') {
ExampleService.model[uri] = this.http.get(uri).map(response => response.json()) // additionally do some magic here, it is Observable<any> instance now
}
return ExampleService.model[uri]
}
}
Summary: according to Günter Zöchbauer answer above solution is not possible, instead of that I need to use something like this:
public get(uri: string): Observable<any> {
return new Observable(observer => {
if (!ExampleService.model[uri]) {
let sub = this.http.get(uri).map(response => response.json()).subscribe(
src => observer.next(ExampleService.model[uri] = src),
err => console.error(err),
() => observer.complete()
)
return () => sub.unsubscribe()
}
observer.next(ExampleService.model[uri])
observer.complete()
})
}
This is not possible because the HTTP request is async and the get() method returns before the call to the server is even made. Instead when the response from the server arrives the callback passed to subscribe(...) is called.
There is no way to go back from async to sync execution.
You can only return the observable for the caller to subscribe to it and do something when the response arrives.

Can I catch certain errors before "subscribe()" in an RXJS observable in Angular2?

Is it possible for a base class to catch certain errors before allowing the subclass to subscribe to the observable in Angular2.
e.g.
export class SomeBaseClass {
constructor(private _http: Http, private _location: Location) {}
protected _fetchData(url): Observable<any> {
const headers = new Headers();
headers.append('Authorization', 'Token foo');
return this._http.get(url, {headers})
.map(response => response.json())
.catch(response => this._handle401(error));
}
private _handle401(response: Response) {
if(response.status === 401) {
this._location.go('/login');
}
// What should this return?
}
}
export class SomeClass extends SomeBaseClass {
constructor( _http: Http, _location: Location) {
super(_http, _location);
}
doTheThing() {
this._fetchData('/someUrl')
.subscribe(
response => this._handleResponse(response),
error => this._handleErrorThatIsNot401(error));
}
private _handleResponse(response) {
// ...
}
private _handleErrorThatIsNot401(error) {
// ...
}
}
Is catch what I am looking for? Should I be using map (or something else)? Or am I going about this the wrong way entirely?
Update
Both answers (so far) put me on the right track, ultimately - I solved it like this:
protected _get(url: string, data?: any): Observable<any> {
return super._get(url, data, this._authorizationHeader)
.map(response => response.json())
.catch(response => this._handle401(response));
}
private _handle401(response: Response): Observable<any> {
try {
if(response.status === 401) {
this._router.navigateByUrl('/login');
return Observable.throw(response.status);
}
} catch(err) {
console.warn('AuthenticatedHttpService._handle401');
console.error(err);
}
return Observable.of(response);
}
Using catch alone does not help much since you have client code subscribed and you must return Observable from catch.
I would implement it as follows:
Rx.Observable.of(42)
.do(v=>{throw new Error('test')})
.catch(Rx.Observable.of(undefined))
.filter(v=>{return v !== undefined})
.subscribe(
(e)=>{console.log('next', e)},
(e)=>{console.log('error', e)},
()=>{console.log('complete')}
);
.catch() is the right one.
Observable is lazy, so there are no errors before you subscribe. Not sure if you mean this kind of "before" therefore I mention it just to be sure.

Categories

Resources