I have the following method that I'm trying to complete:
getAllValues: function (callback) {
this.getCount((count) => { // count is usually 5
let results = []
for (var i = 0; i < count; i++) {
this.getValue(i, (result) => { // getValue() is async and eventually returns a string
results.push(result)
})
if (i == count-1) {
callback(results)
}
}
I want results to be an array with all of the strings returned by getValue(); however, I haven't been able to figure out how to do this. In callback(results), results ends up being an empty array, so the pushed values are being dropped somehow
How do I get this to do what I want?
EDIT: I do NOT want to use promises here.
You're testing for the results completion in the wrong place
getAllValues: function(callback) {
this.getCount((count) => { // count is usually 5
let results = [];
let completed = 0;
for (let i = 0; i < count; i++) { // *** use let instead
this.getValue(i, (result) => { // getValue() is async and eventually returns a string
completed ++;
results[i] = result; // *** note this change to guarantee the order of results is preserved
if (completed == count) {
callback(results)
}
})
}
})
}
Note: use let in the for loop, so that i is correct inside
don't push ... assign to the index, to preserve order of results
and alternative, by having a "promisified" getValue (called it getValuePromise in the code below)
getValuePromise: function(i) {
return new Promise(resolve => {
this.getValue(i, resolve);
});
}
getAllValues: function(callback) {
this.getCount((count) =>
Promise.all(Array.from({length:count}).map((unused, i) => this.getValuePromise(i)))
.then(callback)
);
}
What you need to do is use then() it will wait for your async function to finish then it will run your callback.
getAllValues: function (callback) {
this.getCount((count) => { // count is usually 5
let results = []
for (var i = 0; i < count; i++) {
this.getValue(i, (result) => { // getValue() is async and eventually returns a string
results.push(result)
}).then(function(getValueResults){
if (i == count-1) {
callback(results)
}
})
}
Related
I have a react component that runs this function on the mounting of the component.
function getListOfItems(){
let result = [];
for(let i=0 ; i<5 ; i++){
/**
* code to assign values to some variables namely param1,param2
*/
getDetails(param1,param2);
}
const getDetails = async (param1,param2) => {
let list = await getAPIresults(param1)
result.push(list);
if(result.length === 5){
//code to update a hook which causes render and displays the text in results array
}
}
}
useEffect(() => {
getListOfItems()
},[])
So the code is running but the results array has data in random order. For instance, the results array might look something like this [2,5,1,3,4] where I expect it to be like this [1,2,3,4,5] This means the above code is not running async tasks in the order of their arrival. So could someone help me out to fix this, I want the code to make async requests in order of their arrival.
You need to use the await keyword again to wait for each iteration of the loop to complete before it moves on to the next go-round.
await getDetails(param1,param2)
But as you can only do this in an async function, your getListOfItems will also need to be an async function.
async function getListOfItems(){
let result = [];
for(let i=0 ; i<5 ; i++){
await getDetails(param1,param2);
}
const getDetails = async (param1,param2) => {
let list = await getAPIresults(param1)
result.push(list);
if(result.length === 5){}
}
}
So the code is running but the results array has data in random order.
That's because your loop calls getDetails repeatedly without waiting for the previous call to complete. So all the calls overlap and race.
If it's okay that they overlap but you need the results in order, use Promise.all and have getDetails return its results (rather than pushing them directly).
If you can't make getListOfItems an async function:
const getDetails = async (param1,param2) => {
let list = await getAPIresults(param1)
if(result.length === 5){
//code to update a hook which causes render and displays the text in results array
}
return list;
}
const promises = [];
for (let i = 0; i < 5; ++i) {
promises.push(getDetails(param1, param2));
}
Promise.all(promises)
.then(results => {
// `results` is an array of the results, in the same order as the
// array of promises
})
.catch(error => {
// Handle/report error
});
If you can (and the caller will handle any error that's propagated to it via rejection of the promise from getListOfItems):
const getDetails = async (param1,param2) => {
let list = await getAPIresults(param1)
if(result.length === 5){
//code to update a hook which causes render and displays the text in results array
}
return list;
}
const promises = [];
for (let i = 0; i < 5; ++i) {
promises.push(getDetails(param1, param2));
}
const results = await Promise.all(promises)
// `results` is an array of the results, in the same order as the
// array of promises
If you need them not to overlap but instead to run one after another, your best bet is to use an async function for the loop.
If you can't make getListOfItems an async function:
const getAllResults = async function() {
const results = [];
for (let i = 0; i < 5; ++i) {
results.push(await getDetails(param1, param2));
}
return results;
}
const getDetails = async (param1,param2) => {
let list = await getAPIresults(param1)
if(result.length === 5){
//code to update a hook which causes render and displays the text in results array
}
return list;
}
getAllResults()
.then(results => {
// `results` is an array of the results, in order
})
.catch(error => {
// Handle/report error
});
If you can (and the caller will handle any error that's propagated to it via rejection of the promise from getListOfItems):
const results = [];
for (let i = 0; i < 5; ++i) {
results.push(await getDetails(param1, param2));
}
// Use `results
const getDetails = async (param1,param2) => {
let list = await getAPIresults(param1)
if(result.length === 5){
//code to update a hook which causes render and displays the text in results array
}
return list;
}
You might want to use Promise.all; this would preserve the order as well:
Promise.all: Order of resolved values
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
});
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)
})
I'm tackling a project that requires me to use JavaScript with an API method call. I'm a Java programmer who has never done web development before so I'm having some trouble with it.
This API method is asynchronous and it's in a while loop. If it returns an empty array, the while loop finishes. Otherwise, it loops. Code:
var done = true;
do
{
async_api_call(
"method.name",
{
// Do stuff.
},
function(result)
{
if(result.error())
{
console.error(result.error());
}
else
{
// Sets the boolean to true if the returned array is empty, or false otherwise.
done = (result.data().length === 0) ? true : false;
}
}
);
} while (!done);
This doesn't work. The loop ends before the value of "done" is updated. I've done some reading up on the subject and it appears I need to use promises or callbacks because the API call is asynchronous, but I can't understand how to apply them to the code I have above.
Help would be appreciated!
edit: see the bottom, there is the real answer.
I encourage you yo use the Promise API. Your problem can be solved using a Promise.all call:
let promises = [];
while(something){
promises.push(new Promise((r, j) => {
YourAsyncCall(() => r());
});
}
//Then this returns a promise that will resolve when ALL are so.
Promise.all(promises).then(() => {
//All operations done
});
The syntax is in es6, here is the es5 equivalent (Promise API may be included externally):
var promises = [];
while(something){
promises.push(new Promise(function(r, j){
YourAsyncCall(function(){ r(); });
});
}
//Then this returns a promise that will resolve when ALL are so.
Promise.all(promises).then(function(){
//All operations done
});
You can also make your api call return the promise and push it directly to the promise array.
If you don't want to edit the api_call_method you can always wrap your code in a new promise and call the method resolve when it finishes.
edit: I have seen now the point of your code, sorry. I've just realized that Promise.all will not solve the problem.
You shall put what you posted (excluding the while loop and the control value) inside a function, and depending on the condition calling it again.
Then, all can be wraped inside a promise in order to make the external code aware of this asynchronous execution. I'll post some sample code later with my PC.
So the good answer
You can use a promise to control the flow of your application and use recursion instead of the while loop:
function asyncOp(resolve, reject) {
//If you're using NodeJS you can use Es6 syntax:
async_api_call("method.name", {}, (result) => {
if(result.error()) {
console.error(result.error());
reject(result.error()); //You can reject the promise, this is optional.
} else {
//If your operation succeeds, resolve the promise and don't call again.
if (result.data().length === 0) {
asyncOp(resolve); //Try again
} else {
resolve(result); //Resolve the promise, pass the result.
}
}
});
}
new Promise((r, j) => {
asyncOp(r, j);
}).then((result) => {
//This will call if your algorithm succeeds!
});
/*
* Please note that "(...) => {}" equivals to "function(...){}"
*/
sigmasoldier's solution is correct, just wanted to share the ES6 version with async / await:
const asyncFunction = (t) => new Promise(resolve => setTimeout(resolve, t));
const getData = async (resolve, reject, count) => {
console.log('waiting');
await asyncFunction(3000);
console.log('finshed waiting');
count++;
if (count < 2) {
getData(resolve, reject, count);
} else {
return resolve();
}
}
const runScript = async () => {
await new Promise((r, j) => getData(r, j, 0));
console.log('finished');
};
runScript();
If you don't want to use recursion you can change your while loop into a for of loop and use a generator function for maintaining done state. Here's a simple example where the for of loop will wait for the async function until we've had 5 iterations and then done is flipped to true. You should be able to update this concept to set your done variable to true when your webservice calls have buffered all of your data rows.
let done = false;
let count = 0;
const whileGenerator = function* () {
while (!done) {
yield count;
}
};
const asyncFunction = async function(){
await new Promise(resolve => { setTimeout(resolve); });
};
const main = new Promise(async (resolve)=>{
for (let i of whileGenerator()){
console.log(i);
await asyncFunction();
count++;
if (count === 5){
done = true;
}
}
resolve();
});
main.then(()=>{
console.log('all done!');
});
Also you may try recursion solution.
function asyncCall(cb) {
// Some async operation
}
function responseHandler(result) {
if (result.error()) {
console.error(result.error());
} else if(result.data() && result.data().length) {
asyncCall(responseHandler);
}
}
asyncCall(responseHandler);
Here is a solution I came up with. Place this in an async function.
let finished = false;
const loop = async () => {
return new Promise(async (resolve, reject) => {
const inner = async () => {
if (!finished) {
//insert loop code here
if (xxx is done) { //insert this in your loop code after task is complete
finshed = true;
resolve();
} else {
return inner();
}
}
}
await inner();
})
}
await loop();
If you don't want to use Promises you can restructure your code like so:
var tasks = [];
var index = 0;
function processNextTask()
{
if(++index == tasks.length)
{
// no more tasks
return;
}
async_api_call(
"method.name",
{
// Do stuff.
},
function(result)
{
if(result.error())
{
console.error(result.error());
}
else
{
// process data
setTimeout(processNextTask);
}
}
);
}
Your loop won't work, because it is sync, your async task is async, so the loop will finish before the async task can even respond. I'd reccomend you to use Promises to manage async tasks:
//first wrapping your API into a promise
var async_api_call_promise = function(methodName, someObject){
return new Promise((resolve, reject) => {
async_api_call(methodName, someObject, function(result){
if(result.error()){
reject( result.error() )
}else{
resolve( result.data() )
}
});
})
}
now to your polling code:
//a local utility because I don't want to repeat myself
var poll = () => async_api_call_promise("method.name", {/*Do stuff.*/});
//your pulling operation
poll().then(
data => data.length === 0 || poll(), //true || tryAgain
err => {
console.error(err);
return poll();
}
).then((done) => {
//done === true
//here you put the code that has to wait for your "loop" to finish
});
Why Promises? Because they do state-management of async operations. Why implement that yourself?
let taskPool = new Promise(function(resolve, reject) {
resolve("Success!");
});
let that = this;
while (index < this.totalPieces) {
end = start + thisPartSize;
if (end > filesize) {
end = filesize;
thisPartSize = filesize - start;
}
taskPool.then(() => {
that.worker(start, end, index, thisPartSize);
});
index++;
start = end;
}
After a succesful query to Mongodb to get a list of news, for the news that have a link attached i search for link details in the database but after i set them inside the modified news object i cannot push it to an array. Why does this happen?
var newsarray = []
for(i=0;i<newsfound.length;i++){
if(!newsfound[i].link._id){
newsarray.push(newsfound[i])
} else {
var tempnew = newsfound[i];
db.findOne('links',{'_id':tempnew.link._id},function(err,linkdetails){
if(err){
console.log(err)
} else {
tempnew.linkdetails = linkdetails;
newsarray.push(tempnew)
}
})
}
}
console.log(newsarray)
The result of this is an array without the link containing news or if i try a few changes an array with the original news without the added details.
You can not use an asynchronous function inside a for loop. The reason is that the for loop gets executed before even any callback could come, and hence the scope gets messed up.
You should use recursive function (self calling) which will prevent the next async call before the previous is over.
var i = 0;
function fn() {
// your logic goes here,
// make an async function call or whatever. I have shown async in a timeout.
setTimeout(function () {
i += 1;
if (i < newsfound.length) {
fn();
} else {
// done
// print result
}
});
}
Update:
For your use case,
var newsarray = []
var i = 0;
function done() {
// done, use the result
console.log(newsarray);
}
function fn() {
if (!newsfound[i].link._id) {
newsarray.push(newsfound[i]);
i += 1;
if (i < newsfound.length) {
fn();
} else {
done();
}
} else {
var tempnew = newsfound[i];
db.findOne('links', {'_id':tempnew.link._id}, function(err, linkdetails){
if(err){
console.log(err)
} else {
tempnew.linkdetails = linkdetails;
newsarray.push(tempnew);
i += 1;
if (i < newsfound.length) {
fn();
} else {
done();
}
}
})
}
}