How to handle asynchronous axios api call - javascript

I'm trying to add a property to the all allIco's object from the tgram array / axios api call. When I add the property using .map its value is undefined. I know it's because my api call is asynchronous but I can't figure out how to add an axios.all...any help would be appreciated :)
var allIcos = Object.assign(ico.results);
let tgram = [];
var result = [];
for (let i = 0; i < allIcos.length; i++) {
axios.get(`url=#${tgramUrl[allIcos[i].name]}`).then(response => {
tgram.push(response.data.result);
}).catch(err => console.log(err));
}
var result = allIcos.map((obj,i) => Object.assign({telegram:"test"}, obj));
this.setState({data: allIcos});
console.log(allIcos);

what about try promise.all?
const promises = [];
for (let i = 0; i < allIcos.length; i++) {
promises.push(axios.get(`url=#${tgramUrl[allIcos[i].name]}`));
}
Promise.all(promises).then(function(values) {
console.log(values);
});
reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

The only way I see is to perform map and setState every time there is a response from the axios call (inside the 'then' clause. Unless you can make one axios call for all (and then, the setState and map should also be inside the 'then', but you know that).

Related

Why promise.all crash an error but not sending promises one by one?

I'm pretty new here and I have a problem on my backend nodejs.
I have a list of object that will help me find a car in the database, and so I prepare promises to get data one by one and send that inside a promise.all to trigger the promises.
the function getCar is working every time with data I sent but When I do the promise.all with the array then it will have an error of pool of connection. What is the probleme ?
function requestData (listOfCar) {
const promiseList = [];
for (let i = 0; i < listOfCar.length; i++) {
promiseList.push(getCar(listOfCar[i]));
}
return Promise.all(promiseList); // crash but sending promises one by one is working
}
function getCar(carFinder) {
// do things and return a query find in sequelize to find a car
// and so it return a promise
}
Promise are always directly trigger, they do not wait to be trigger inside the promise.all.
So your problem is that you are sending I guess way to many request inside the database and so the pool of connection do not accept it anymore
To fix that you can add more pool of connection or you can simply trigger promise little chunk of 5 and await the promise.all
async function requestData (listOfCar) {
const carLists = [];
for (let i = 0; i < listOfCar.length; i++) {
const tmpListOfPromise = [];
for (let j = 0; j < 5 && i < listOfCar; j++) {
const tmpListOfPromise.push(getCar(listOfCar[i]));
i = i + 1;
}
await Promise.all(tmpListOfPromise).then((response) => {
carLists = carLists.concat(response); // this will push every response in the list
});
}
}
function getCar(carFinder) {
// do things and return a query find in sequelize to find a car
// and so it return a promise
}

RxJS Observable forkJoin Not Executing in Parallel

I have the following code and, for the life of me, can't figure out why the requests don't execute concurrently. I'm still new to RxJS and observables, so any help in improving the code below would be greatly appreciated as well. Basically, I'm making a call to a REST API on the backend to get some data. Then, for every element in that array of data I'm making another request to a different endpoint (hence using the 'forkJoin' operator). All the requests get sent at once, but they seem to execute one after another still instead of concurrently.
this.sites$.subscribe(data => {
// data.forEach(element => {
// this.siteCaptureMap[element.id] = new CaptureData();
// this.sitesService.getCaptureData(element.nameOrNumber, element.owner.name).subscribe(data => {
// this.siteCaptureMap[element.id].count = data.length;
// });
// });
var obs: Observable<any>[] = [];
for (var _i = 0; _i < data.length; _i++) {
this.siteCaptureMap[data[_i].id] = new CaptureData();
this.siteCaptureMap[data[_i].id].id = _i;
obs.push(this.sitesService.getCaptureData(data[_i].nameOrNumber, data[_i].owner.name));
}
forkJoin(obs).subscribe(results => {
for (var _i = 0; _i < results.length; _i++) {
this.siteCaptureMap[data[_i].id].count = results[_i].length;
}
});
this.dataSource.data = data;
this.dataSource.filteredData = data;
});
Again, any help would be greatly appreciated. If I need to clarify anything or provide any additional code snippets, please let me know! Thanks!
Having nested subscribes can lead to memory leaks and can be tough to unsubscribe so whenever you have nested subscribes, think of switchMap, concatMap, and mergeMap.
They all have small variations but they switch to a new observable from the previous observable. This post explains the differences.
For you, I would try doing:
import { switchMap } from 'rxjs/operators';
...
this.sites$.pipe(
switchMap(data => {
let obs: Observable<any>[] = [];
for (let _i = 0; _i < data.length; _i++) {
this.siteCaptureMap[data[_i].id] = new CaptureData();
this.siteCaptureMap[data[_i].id].id = _i;
obs.push(this.sitesService.getCaptureData(data[_i].nameOrNumber, data[_i].owner.name));
}
return forkJoin(obs);
}),
).subscribe(results => {
for (let_i = 0; _i < results.length; _i++) {
this.siteCaptureMap[data[_i].id].count = results[_i].length;
}
this.dataSource.data = data;
this.dataSource.filteredData = data;
});
A side note is to use let and const instead of var.
Also, if you see all the requests going out at the same time, that's all you can hope for. If it returns in series, it can either be the browser or the server causing that.
First I would rearrange the code to be more rxjs idiomatic, removing nested subscriptions and using pipe and making it a bit more functional-style. Inline comments try to explain the changes
this.sites$.pipe(
// this is the rxjs map operator that transform the data received from
// the rest api into something different
map(data => {
// I create the obs array using the map method of JS arrays - this is
// NOT the rxjs map operator
obs = data.map((element, _i) => {
this.siteCaptureMap[element.id] = new CaptureData();
this.siteCaptureMap[element.id].id = _i;
return this.sitesService.getCaptureData(element.nameOrNumber, element.owner.name)
});
// return both the array of observables and the data
return [data, obs];
}),
// concatMap makes sure that you wait for the completion of the rest api
// call and the emission of the data fetched before you execute another
// async operation via forkJoin
concatMap(([data, obs]) => forkJoin(obs).pipe(
// this is the rxjs map operator used to return not only the results
// received as result of forkJoin, but also the data received with the
// first rest call - I use this operator to avoid having to define
// a variable where to store 'data' and keep the logic stateless
map(results => ([data, results]))
))
).subscribe(([data, results]) => {
// here I use forEach to loop through the results and perform
// your logic
results.forEach((res, _i) => this.siteCaptureMap[data[_i].id].count = res.length)
})
I think this form is more in line with rxjs and its functional spirit, and should be equivalent to your original form.
At the same time I would be interested to know why you say that your code executes the calls within forkJoin one at the time.
By the way, if you are interested in looking at common patterns of use of rxjs with http, you can read this article.

Javascript await inside a loop

I am trying to work with an api where I have to send a request for each item in a list.
However, I see that the loop doesn't seem to wait for every request, i.e, the loop doesn't work as expected. Here's the code below
getInfo = async () => {
const mylist = ["item1","item2","item3","item4","item5","item6","item7"]
const responses = []
const len = mylist.length
for (let i = 0; i < len; i++) {
//console.log("inside loop")
await axios.get("some_url/"+mylist[i])
.then(res => {
responses.push(res.data)
})
}
When I run the program, all the console.log("inside loop") executes immediately without waiting for the request to be complete.
How can I modify the code so as to wait for each response to be completed before updating the for loop counter variable?
You could try re-arranging the code to something like this. But using a Promise.all with Array.prototype.map would be more idiomatic solution for the problem.
await the async call (remove unnecessary .then call) and then console.log
getInfo = async () => {
const mylist = ["item1","item2","item3","item4","item5","item6","item7"]
const responses = []
const len = mylist.length
for (let i = 0; i < len; i++) {
responses.push((await axios.get("some_url/"+mylist[i])).data)
console.log("inside loop")
}
}
Internally, await is translated into a Promise chain. Since the for loop can't be transformed into a Promise-continuation, you'll need to convert it to a Promise-based construct.
Depending on what you want to achieve there are multiple ways to go about it.
Constructing the responses array could be done with a map statement.
const promises = mylist.map(item => {
return axios.get("some_url/"+item).then(res => { return res.data; })
});
const data = await Promise.all(promises);
No manual pushing items around or fiddling with the array length.

Make Synchronus Api inside for Loop

I have an array of Objects/String.
this.addtenant().subscribe(r => {
let a = ['plant1', 'plant2'];
for (let b of a) {
this.myService.saveAllData(b).subscribe(r => {
console.log("result" r);
});
}
});
and getAllData is in myservice file which returns an Observable.
saveAlldata(b) {
this.http.post(url,b);
}
The problem is since i am using subscribe the call is being asynchronus, i want to have it something like:
first "plant1" post call has to finish, and then plant2 has to be made. In simple words synchronus call.
I think you should use async/await for synchronous calls.
Here is one of the tutorial:
https://www.techiediaries.com/javascript-async-await-tutorial/
A sample code demo would be something like:
responseArr: any[] = [];
func1()
let x = [1,2,3];
func2(x);
}
async func2(arr) { // make the function async
for(let i=0; i<arr.length ; i++){
const response: any = await this.myService(arr[i]); // wait on each service call. This will give synchronous behaviour
this.handleResponse(response); // handle response in a function if there is common functionality in each response
}
reuturn responseArr; // return the final response
}
handleResponse(response) {
responseArr.push(response);
}
I have found a solution by myself. You can use an async await inside a loop, since forEach is not promise aware and cannot be used with Promise. Here is the final code:
this.addtenant().subscribe(async r => {
let a = ['plant1', 'plant2'];
for(let index=0; index=a.length; index++)
await this.myService.saveAllData(b).toPromise().then(r => {
console.log("result" r);
});
}
}
});
As await works with Promise, so toPromise will replace subscribe()

asyncronous callbacks inside for loop

var a = ['url1', 'url2', 'url3'];
var op = [];
cb = (callback) => {
for (var i = 0; i < a.length; i++) {
gtts.savetos3(`${a[i]}.mp3`, a[i], 'ta', function(url) {
console.log(url['Location']);
op.push(url['Location']);
});
}
callback()
}
cb(() => {
console.log(op);
})
In the above code the gtts.savetos3 is an asynchronous function.It takes significance amount of time to complete the execution for every element in array.Due to asynchronous feature I cannot print the complete array of url in op array as it prints an empty array.
gtts.savetos3 function calls the given callback with correct url so that i can print the output using console.log but when comes to looping i got messed up.
My question is
How to make the callback function called only after the execution of
the all array elements get processed by the gtts.savetos3 function.
can we achieve the solution for above problem without Promise.all or without Promise with the help of using only callbacks.
Thanks in Advance ...!
You can keep a counter and increase it inside the methods's callback,
call your done callback only when the counter reaches the length of
the array.
cb = (done) => {
let counter = 0;
for (let i = 0; i < a.length; i++) {
gtts.savetos3(`${a[i]}.mp3`, a[i], 'ta', function (url) {
console.log(url['Location']);
op.push(url['Location']);
++counter;
if (counter == a.length) {
done();
}
});
}
}
cb(() => {
console.log(op);
})
This is just a way to solve the problem without Promises or any third party module but not the elegant or correct way.
If you want to stick to the callback and are ok to use third-party module have a look on
Async waterfall method.
If you are using aws-sdk's s3 put object, then sdk already provides a
promisified methods as well, you can simply append your method with
.promise to get the same.
To solve the problem with promises just change your wrapper into an
async function.
async savetos3(...parametres) {
//Some implementation
let res = await S3.putObject(....params).promise();
//Some implementation
}
cb = Promise.all(a.map(name => savetos3(`${name}.mp3`, name , 'ta')));
cb.then(() => {
console.log(op);
})
Here is my solution.
const a = ['url1', 'url2', 'url3'];
const op = [];
const saveToS3 = name => new Promise((resolve, reject) => {
gtts.savetos3(`${name}.mp3`, name, 'ta', function (url) {
console.log(url['Location']);
resolve(url)
});
})
Promise.all(a.map(item => saveToS3(item))).then(() => {
console.log(op)
})

Categories

Resources