Im refactoring a billing system made with Angular + NodeJs.
I need to make some API calls to my backend, first to get Customers, then Invoices and finally Students. I call the functions in ngOnInit. Then I need to manipulate the Arrays mixing the data.
If I call the functions in order, sometimes the code can't access all the info, for example:
ngOnInit(): void {
this.getCustomers();
this.getInvoices();
this.getStudents();
this.doSomeCalculations();
Sometimes customers[] (defined by this.getCustomers() is still empty when this.doSomeCalculations() starts.
I tried with setTimeout, with no improvement.
Finally what I did to make everything work was calling getInvoices() in the getCustomers() response, and getStudents() in getInvoices() response. So getInvoices() asign the response to invoices[] and then calls the next function, and so on. And doSomeCalculations() only starts when all the data has been asigned to variables.
But this solution is ugly. I know there is a better way of doing this, but I don't know how. Maybe it is related to promises or async-await?
My base code for the API calls is as follows:
getCustomers(){
this._customerService.getCustomers()
.subscribe(
{ next: (res) => {
this.customers = res;
this.getInvoices();
},
error: (err) => {
console.log(<any>err);
}
}
);
}
It is the same for getInvoices and getStudents.
How can I improve my code here?
You could try using async/await + promises.
For each method definition, you could return the data as a promise, then use async/await at the top level. I see you're using observables for the data calls, but you don't have to. Promises may be better here.
Example:
async ngOnInit(): Promise<void> {
try {
await getCustomers();
...
} ...
}
getCustomers(): Promise<Customers[]>{
return new Promise((err, resp) => {
...
resolve(resp)
})
}
Related
Good day.
Im working on a express project and havent been ables to fully understan how async functions an promises work. This is my project structure
-controllers
--userController.js
-services
--paymentService.js
payment service is the implementation of a third-party payment tool, and it defines a method im using like this
const pay = async(data) => {
service.pay()
.then(function(response){
return response;
}).catch(function(err)){
console.log(err);
});
}
im calling this method from my userController
let payUser = async(req, res) => {
const response = await paymentService.pay(req.data);
if(response) {
res.send(response)
}
}
but response is always undefined, it doesnt wait as i thought it shoudl do because of the "await" keyword.
How does this exactly works? what am i doing wrong if i want to make it wait till the response is returned?
There's an issue in your code for the pay function:
const pay = async(data) => {
service.pay()
.then(function(response){
return response;
}).catch(function(err)){
console.log(err);
});
}
You don't return anything. You just run service.pay(). Therefore, the signature of the function pay (declared on the first line) is a function that returns a promise, but the return type is Promise<void>, not Promise<YourData>. This causes it to always return undefined, even when called with await, like in your complete example code.
I notice you have the code return response; inside the "then" callback. This may mean you intended for that data to be returned to the caller of pay (not service.pay) when the data was ready. To do this, should rewrite your pay function in one of two ways:
Replace service.pay ... with return service.pay ... so that the promise returned by service.pay is actually returned to the caller of pay.
Rewrite it so that you remove the "then" callback and replace service.pay ... with return await service.pay .... You've already put in the work to design pay as an async function, so you can now use await inside it, which often makes this kind of code more straightforward to write and read later on.
Given this method:
public logIn(data:any): Observable<any> {
this.http.get('https://api.myapp.com/csrf-cookie').subscribe(() => {
return this.http.post('https://api.myapp.com/login', data);
});
}
I would like it to return that nested observable, so that my calling code can use it like so:
this.apiService.logIn(credentials).subscribe(() => {
// redirect user to their dashboard
});
without needing to know about the first /csrf-cookie request. Obviously the above doesn't work - but I'm struggling to understand how to make the inner HTTP request wait for the outer one to finish AND be returned by the method.
you should use switchMap see the documentation on switch map
public logIn(data:any): Observable<any> {
return this.http.get('https://api.myapp.com/csrf-cookie').pipe(
switchMap(x => this.http.post('https://api.myapp.com/login', data))
);
}
with rxjs nested subscribes are generally not a good idea. There are many great operators within the library that will get you around it. In this case above where one call depends on another switchMap(...) is the best fit.
Also the code has been modified to return the observable not the subscription
So I have an Angular component.
With some array objects containing data I want to work with:
books: Book[] = [];
reviews: Review[] = [];
This is what my ngOnInit() looks like:
ngOnInit(): void {
this.retrieveBooks();
this.retrieveReviews();
this.setRatingToTen();
}
With this I write Books, and Reviews to object arrays.
This is done through a "subscription" to data through services:
retrieveBooks(): void {
this.bookService.getAll()
.subscribe(
data => {
this.books = data;
},
error => {
console.log(error);
}
);
}
retrieveReviews(): void {
this.reviewService.getAll()
.subscribe(
data => {
this.reviews = data;
},
error => {
console.log(error);
});
}
So this next function I have is just an example of "working with the data".
In this example, I just want to change all of the totalratings to 10 for each Book:
setRatingToTen(): void {
this.books.forEach(element => {
element.totalrating = 10;
});
console.log(this.books);
}
The problem I have been trying to wrap my head around is this:
this.books is an empty array.
I THINK the reason is because this function is running before the data subscription.
IF this is the case, then my understanding of ngOnInit must not be right.
I thought it would call the function in order.
Maybe that's still the case, it's just that they don't complete in order.
So my questions are:
1. Why is it an empty array?
(was I right? or is there more to it?)
2. How do Angular developers write functions so they operate in a desired order?
(since the data needs to be there so I can work with it, how do I avoid this issue?)
(3.) BONUS question:
(if you have the time, please and thank you)
My goal is to pull this.reviews.rating for each book where this.reviews.title equals this.books.title, get an average score; and then overwrite the "0" placeholder of this.books.totalrating with the average. How could I re-write the setRatingToTen() function to accomplish this?
Here is one of solution using forkJoin method in rxjs .
you can check this for details https://medium.com/#swarnakishore/performing-multiple-http-requests-in-angular-4-5-with-forkjoin-74f3ac166d61
Working demo : enter link description here
ngOnInit:
ngOnInit(){
this.requestDataFromMultipleSources().subscribe(resList => {
this.books = resList[0];
this.reviews = resList[1];
this.setRatingToTen(this.books,this.reviews);
})
}
forkJoin method:
public requestDataFromMultipleSources(): Observable<any[]> {
let response1 = this.retrieveBooks();
let response2 = this.retrieveReviews();
// Observable.forkJoin (RxJS 5) changes to just forkJoin() in RxJS 6
return forkJoin([response1, response2]);
}
Other methods:
retrieveBooks(): void {
this.bookService.getAll()
.subscribe(
data => {
this.books = data;
},
error => {
console.log(error);
}
);
}
retrieveReviews(): void {
this.reviewService.getAll()
.subscribe(
data => {
this.reviews = data;
},
error => {
console.log(error);
});
}
setRatingToTen(books, reviews): void {
this.books.forEach(element => {
element.totalrating = 10;
});
console.log(this.books);
}
Angular makes heavy use of observables to handle variety of asynchronous operations. Making server side requests (through HTTP) is one of those.
Your first two questions clearly reflect you are ignoring the asynchronous nature of observables.
Observables are lazy Push collections of multiple values. detailed link
Means observable response would be pushed over time in an asynchronous way. You can not guarantee which of the two distinct functions would return its response first.
Having said that, rxjs library (Observables are also part of this library and angular borrowed them from here) provides a rich collection of operators that you can use to manipulate observables.
With the above explanation, here is one by one answer to your questions.
1. Why is it an empty array?
Because you are thinking in terms of synchronous sequential flow of code, where one method would get called only after the other has finished with its working. But here retrieveBooks and retrieveReviews both are making asynchronous (observable) calls and then subscribing to it. This means there is no guarantee when their response would be received. Meanwhile the hit to setRatingToTen had already been made, at that point in time books array was empty.
2. How do Angular developers write functions so they operate in a desired order?
Angular developer would understand the nature of asynchronous observable calls, and would pipe the operators in an order so that they are sure they have the response in hand before performing any further operation on the observable stream.
(3.) BONUS question:
Your requirement specifies that you must first have the response of both observables at hand before performing any action. For that forkJoin rxjs operator suits your need. Documentation for this operator say
One common use case for this is if you wish to issue multiple requests on page load (or some other event) and only want to take action when a response has been received for all. detailed link
Not sure about your average score strategy, but here is an example code how you would achieve your purpose.
ngOnInit(){
let req1$ = this.bookService.getAll();
let req2$ = this.reviewService.getAll();
forkJoin(req1$, req2$).subscribe(([response1, response2])=>{
for(let book of response1) //loop through book array first
{
for(let review of response2) //inner loop of reviews
{
if(book.title == review.title)
{
//complete your logic here..
}
}
}
});
}
I'm trying to create saving feature for my little game (Javascript, Axios, Express, NodeJS MongoDB). Problem is that I don't really understand axios promises and async/await features or more accurately I don't know how to implement them into my code. I want to get data from my mongodb and use it in variable/method so I can change stats of player etc. later as needed. I have been reading all possible guides and similiar posts, but I have no idea why they don't work for me. Any help would be appreciated!
After messing around and trying everything I found on web, here's currently part of my code:
case "Load":
function getMySave () {
return axios.get("http://localhost:3000/save", {
params: {
name: username
}
})
.then(response => {
console.log(response.data)
return response.data
})
.catch(error => {
console.log(error);
return Promise.reject(error);
});
}
const waitIgetMySave = async () => {
const playerSave = await getMySave();
return playerSave;
};
playerSave = (waitIgetMySave());
console.log(playerSave)
player = new Player(username, playerSave.class, playerSave.health, playerSave.mana, playerSave.strength, playerSave.agility, playerSave.speed, playerSave.maxhp);
break;
}
But this code just returns following:
Promise { : "pending" }
Object { _id: "5e9945f238a82e084c7cb316", name: "Jesse", class: "Rogue", ..... }
So object itself is working fine, but I can't apply it to anything outside of the axios.get function. It always gives me pending, promise[Object], undefined or such that I can't use in my player object or variables. I'm quite sure that I'm doing something wrong with async/await features where, but after spending few days trying to solve this problem, I'm really running out of options.
And yes, I looked at [How do I return the response from an asynchronous call?
[1]: How do I return the response from an asynchronous call? but that seems to be for Ajax and I just fail to understand and implement those in my own code.
The key principles of async code
You cannot make asynchronous code synchronous.
Promises are tools to manage asynchronous code in a consistent way
The async and await keywords are tools to manage Promises in a way that looks synchronous (but really just does clever stuff with functions going to sleep while they wait and allowing the rest of the program to keep running)
The specific issue with your code
waitIgetMySave is defined as async so it returns a Promise
When you call it (playerSave = (waitIgetMySave());) you do not await it, so you get the promise and not the resolved value from the promise.
(And waitIgetMySave will have gone to sleep while it waits for getMySave to resolve. This allows the rest of the program to keep going, assign the promise to playerSave and log that promise in the meantime).
(my case applies to C#, MVC, returning JSON, got jquery, angular), but I expect it applies to more than that.
I have a website where my angular/html/js calls ~7 services through Angular controllers and async-gets/displays data (weather, road conditions, etc). Some of these take longer than others (from ms to ~10s). I'd like to have a single call to my service which returns all of this data - but doesn't wait until the last call to return anything (10s).
Is there a way to make a single call, and return results as I have them and they get displayed accordingly? Do I need to have a repeating call which has a boolean like "IsMore=T" and calls the service again? (doesn't sound efficient).
Ideally, I'd like to keep a response channel open and keeping pumping results until it's done. Possible?
I'm not sure I understand completely, but I think you could just chain the response promises together, something like:
$scope.getWeatherData = function () {
return myWeatherService.get().then(function (resp) {
$scope.weatherData = resp;
});
}
$scope.getTrafficData = function () {
return myTrafficService.get().then(function (resp) {
$scope.trafficData = resp;
});
}
$scope.getWeatherData.then(getTrafficData).then(...chain others...);
This assumes that the service calls return a $http promise.
Anyway, whenever the promise comes in, the data will be on $scope, and hence the display will be updating as the promises arrive. It sounds like you might be using something like $q.all(), which would wait until all promises are resolved.
Buildilng on #reptilicus' answer:
$scope.getWeatherData = function () {
return myWeatherService.get().then(function (resp) {
$scope.weatherData = resp;
});
};
$scope.getTrafficData = function () {
return myTrafficService.get().then(function (resp) {
$scope.trafficData = resp;
});
};
$q.all([
$scope.getWeatherData(),
$scope.getTrafficData()
]).then(function () {
// do whatever's next, or nothing
});
... would request/receive both responses in parallel (if that's what you want). The relevant $scope property for each request will be populated when the related response is received, and the "do whatever's next" code will run once they are all complete.
Note, you need to inject $q to your controller constructor for this to work. :)
Edit: I just noticed that #reptilicus did mention $q.all. The difference between chaining the .thens and $q.all is that under chaining, one request wouldn't start until the previous was received....