asyncronous callbacks inside for loop - javascript

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)
})

Related

Promise.all in loop, one by one. Await until function fully completed [duplicate]

This question already has answers here:
Resolve promises one after another (i.e. in sequence)?
(36 answers)
Closed 1 year ago.
I'm trying to resolve promises one by one, but i don't understand how to. I want to wait till video message load fully then move to another promise, otherwise it freeze browser. In my code example it don't wait until video hit loadedmetadata event, and go to next promise. And after all promises executed, load's all videos. How it looks: https://prnt.sc/10mg4oc
//All messages loop
for (var i = 0; i < data[0].length; i++) {
let urls = [];
$.each(file_id.split(','), function( index, value ) {
file_id = value;
urls.push("//localhost/storage/"+data[0][i].user_id+"/"+data[0][i].file_cluster_id+"/"+value)
});
//Get chunks array from file cluster
let chunkPromises = urls.map(function callback(url,index) {
return fetch(url).then((res) => {
return res.arrayBuffer();
});
});
console.log(chunkPromises);
console.log(0);
//I'm trying to understand how to do it
(async function loop() {
await Promise.all(chunkPromises).then(chunks => {
console.log(chunks);
console.log(1);
//Appending file chunks to file back
for (const value of chunks) {
console.log(2);
}
video.attr('src', URL.createObjectURL(full_video));
console.log(3);
//I want that promise complete loadedmetadata, then go to another promise iteration
video.on("loadedmetadata", function () {
console.log(`${secondsReady} seconds of video are ready to play.`);
});
});
});
}
If it possible i woold like, not touch all messages loop.
You can actually use async/await in for loops, it doesn't work well with other forloop iteration like forEach, filter etc. :
const PromiseMe = () => {
return new Promise((res, rej) => {
setTimeout(() => {
res('Integrate Me');
}, 1000)
});
}
const main = async () => {
for(let i = 0; i< 10; i++) {
let val = await PromiseMe();
console.log(val);
}
}
main();
So, you can actually implement this in you for-loop like this.
const result = [];
const mockFunction = async () => {
for (var i = 0; i < data[0].length; i++) {
//Get chunks array from file cluster
//urls is an array
let chunkPromises = await fetch(url[i]);
result.push(chunkPromises); //parse this data to specific format if needed
}
console.log(result);

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()

how to validate all refs with vee-validate?

I have dynamic components that needs to be validated.
I have an array and i push my components there. The for loop works great.
validateForm() {
const PROMISES = [this.$refs.contactDetailsForm.$refs.contactDetails]
for (let i = 1; i <= this.count; i++) {
PROMISES.push(this.$refs[`passengerForm${i}`][0])
}
return Promise.all(PROMISES)
},
But problem is I do not know how to return the results of the validation. I want the results of this function in another function(Promise). how can I do that?
this is the solution:
validateForm() {
const PROMISES = [this.$refs.contactDetailsForm.$refs.contactDetails]
for (let i = 1; i <= this.count; i++) {
PROMISES.push(this.$refs[`passengerForm${i}`][0])
}
return new Promise((resolve, reject) => {
PROMISES.forEach(async (item) => {
const STATUS = await item.validate()
STATUS ? resolve(STATUS) : reject(STATUS)
})
})
}
Untested, but Promise.all returns an array of results for the promises. What you need to do is trigger validate for all the things you want to know the result for, collect those promises and then check the results in a Promise.all. You didn't give quite enough code to answer this fully but it's something like this:
validateForm() {
//both of these I added validate() to because I'm hoping they are references to ValidationObservers
const PROMISES = [this.$refs.contactDetailsForm.$refs.contactDetails.validate()]
for (let i = 1; i <= this.count; i++) {
PROMISES.push(this.$refs[`passengerForm${i}`][0].validate())
}
return Promise.all(Promises);
}
Then wherever you are calling this, you'd do:
this.validateForm().then((values) => {
this.formIsValid = values.every((result) => result));
//if the things above are ValidationProviders rather than VO, you have to use result.valid instead of result
});

Javascript: Making sure one async function doesn't run until the other one is complete; working with promises

I'm working with fetching information from a github repository. I want to get the list of pull requests within that repo, get the list of commits associated with each pull request, then for each commit I want to get information such as the author of the commit, the number of files associated with each commit and the number of additions and deletions made to each file. I'm using axios and the github API to accomplish this. I know how to work with the API, but the promises and async functions are keeping me from accomplishing my task. I have the following code:
const axios = require('axios');
var mapOfInformationObjects = new Map();
var listOfCommits = [];
var listOfSHAs = [];
var gitApiPrefix = link I'll use to start fetching data;
var listOfPullRequestDataObjects = [];
var listOfPullRequestNumbers = [];
var mapOfPullNumberToCommits = new Map();
function getAllPullRequests(gitPullRequestApiLink) {
return new Promise((resolve, reject) => {
axios.get(gitPullRequestApiLink).then((response) =>{
listOfPullRequestDataObjects = response['data'];
var k;
for (k = 0; k < listOfPullRequestDataObjects.length; k++){
listOfPullRequestNumbers.push(listOfPullRequestDataObjects[k]['number']);
}
resolve(listOfPullRequestNumbers);
}).catch((error) => {
reject(error);
})
})
}
function getCommitsForEachPullRequestNumber(listOfPRNumbers) {
var j;
for (j = 0; j < listOfPRNumbers.length; j++) {
currPromise = new Promise((resolve, reject) => {
currentGitApiLink = gitApiPrefix + listOfPRNumbers[j] + "/commits";
axios.get(currentGitApiLink).then((response) => {
mapOfPullNumberToCommits.set(listOfPRNumbers[j], response['data']);
resolve("Done with Pull Request Number: " + listOfPRNumbers[j]);
}).catch((error) => {
reject(error);
})
})
}
}
function getListOfCommits(gitCommitApiLink){
return new Promise((resolve, reject) => {
axios.get(gitCommitApiLink).then((response) => {
resolve(response);
}).catch((error) => {
reject(error);
})
})
}
So far, I made some functions that I would like to call sequentially.
First I'd like to call getAllPullRequestNumbers(someLink)
Then I'd like to call getCommitsForEachPullRequestNumber(listofprnumbers)
Then getListOfCommits(anotherLink)
So it would look something like
getAllPullRequestNumbers(someLink)
getCommitsForEachPullRequestNumber(listofprnumbers)
getListOfCommits(anotherlink)
But two problems arise:
1) I'm not sure if this is how you would call the functions so that the first function in the sequence completes before the other.
2) Because I'm not familiar with Javascript, I'm not sure, especially with the getCommitsForEachPullRequestNumber function since you run a loop and call axios.get() on each iteration of the loop, if this is how you work with promises within the functions.
Would this be how you would go about accomplishing these two tasks? Any help is much appreciated. Thanks!
When you a number of asynchronous operations (represented by promises) that you can run all together and you want to know when they are all done, you use Promise.all(). You collect an array of promises and pass it to Promise.all() and it will tell you when they have all completed or when one of them triggers an error. If all completed, Promise.all() will return a promise that resolves to an array of results (one for each asynchronous operation).
When you're iterating an array to do your set of asynchronous operations, it then works best to use .map() because that helps you create a parallel array of promises that you can feed to Promise.all(). Here's how you do that in getCommitsForEachPullRequestNumber():
function getCommitsForEachPullRequestNumber(listOfPRNumbers) {
let mapOfPullNumberToCommits = new Map();
return Promise.all(listOfPRNumbers.map(item => {
let currentGitApiLink = gitApiPrefix + item + "/commits";
return axios.get(currentGitApiLink).then(response => {
// put data into the map
mapOfPullNumberToCommits.set(item, response.data);
});
})).then(() => {
// make resolved value be the map we created, now that everything is done
return mapOfPullNumberToCommits;
});
}
// usage:
getCommitsForEachPullRequestNumber(list).then(results => {
console.log(results);
}).catch(err => {
console.log(err);
});
Then, in getListOfCommits(), since axios already returns a promise, there is no reason to wrap it in a manually created promise. That is, in fact, consider a promise anti-pattern. Instead, just return the promise that axios already returns. In fact, there's probably not even a reason to have this as a function since one can just use axios.get() directly to achieve the same result:
function getListOfCommits(gitCommitApiLink){
return axios.get(gitCommitApiLink);
}
Then, in getAllPullRequests() it appears you are just doing one axios.get() call and then processing the results. That can be done like this:
function getAllPullRequests(gitPullRequestApiLink) {
return axios.get(gitPullRequestApiLink).then(response => {
let listOfPullRequestDataObjects = response.data;
return listOfPullRequestDataObjects.map(item => {
return item.number;
});
});
}
Now, if you're trying to execute these three operations sequentially in this order:
getAllPullRequests(someLink)
getCommitsForEachPullRequestNumber(listofprnumbers)
getListOfCommits(anotherlink)
You can chain the promises from those three operations together to sequence them:
getAllPullRequests(someLink)
.then(getCommitsForEachPullRequestNumber)
.then(mapOfPullNumberToCommits => {
// not entirely sure what you want to do here, perhaps
// call getListOfCommits on each item in the map?
}).catch(err => {
console.log(err);
});
Or, if you put this code in an async function, then you can use async/awit:
async function getAllCommits(someLink) {
let pullRequests = await getAllPullRequests(someLink);
let mapOfPullNumberToCommits = await getCommitsForEachPullRequestNumber(pullRequests);
// then use getlistOfCommits() somehow to process mapOfPullNumberToCommits
return finalResults;
}
getAllCommits.then(finalResults => {
console.log(finalResults);
}).catch(err => {
console.log(err);
});
not as clean as jfriend00 solution,
but I played with your code and it finally worked
https://repl.it/#gui3/githubApiPromises
you get the list of commits in the variable listOfCommits
I don't understand the purpose of your last function, so I dropped it

Categories

Resources