I have a for loop which is calling an async function. I need this function to then call a callback at the end of the for loop but only when all my async functions have returned their result. I have tried this:
for(var i = 0; i < vaccinsCount; i++){
getVaccinAddress(i, address, provider, function(result){
if(result.success){
console.log("result:" + result.values);
vaccines.push(result.values);
} else {
callback({success: false, message: result.message});
}
});
}
callback({success: true, values: vaccines});
instead what is happening is that the code enters the for loop then call then async function then exits straigh away. How could i get around this ?
getVaccinAddress is the Async Function that does a server call.
EDIT
I am using NodeJS, therefore the solution is to then use bluebird, I however have no idea on how to implement this with bluebird.
I highly recommend using promises in this case.
It's a good way to manage your asynchronous calls:
https://davidwalsh.name/promises
If you are using promises your code would look something like this:
var promises = []
for(var i = 0; i < vaccinsCount; i++){
promises.push(getVaccinAddress(i, address, provider));
// getVaccinAddress will need to return a promise
}
Promise.all(promises).then(function(result) {
console.log('success');
})
.catch(function(err) {
console.log(err);
});
You can call callback() when vaccines.length is equal to vaccinsCount
for(var i = 0; i < vaccinsCount; i++) {
(function(i) {
getVaccinAddress(i, address, provider, function(result) {
if(result.success) {
console.log("result:" + result.values);
vaccines.push(result.values);
if (vaccines.length === vaccinsCount) {
// call `callback()` here
}
}
});
})(i);
}
Related
Suppose you have an Array/Object that contains a list of values. Lets say those a mysql commands or urls or filespaths. Now you want to iterate over all of them and execute some code over every entry.
for(let i = 0; i < urls.length; i++){
doSthWith(urls[i]);
}
No Problem so far. But now lets say each function has a callback and needs the result of the last execution. e.g. you request something from one website and you want to use the results of this request for one of your following requests.
for(let i = 0; i < urls.length; i++){
if(resultOfLastIteration.successful){ //or some other result besides the last one
doSthWith(urls[i]);
}
}
Now lets say the length of urls (or sth similar) is over 100. Thats why you normaly use a loop so you dont need to write the same function a 100 times. That also means that Promises wont do the trick either (except Im unaware trick a trick), because you have the same problem:
doSthWith(urls[0]).then(...
doSthWith(urls[1]).then(... //either put them inside each other
).then(...
doSthWith(urls[i]) //or in sequence
...
).catch(err){...}
Either way I dont see a way to use a loop.
A way that I found but isnt really "good" is to use the package "wait.for"(https://www.npmjs.com/package/wait.for). But what makes this package tricky is to launch a fiber each time you want to use wait.for:
//somewhere you use the function in a fiber Context
wait.for(loopedExecutionOfUrls, urls);
//function declaration
function loopedExecutionOfUrls(urls, cb){
//variables:
for(let i = 0; i < urls.length; i++){
if(someTempResultVar[i-1] === true){
someTempResultVar = wait.for(doSthWith,urls[i]);
} else if(...){...}
}
}
But Im not sure if this approach is really good, besides you always have to check if you have wrapped the whole thing in a Fiber so for each function that has loops with functions that have callbacks. Thus you have 3 levels: the lauchFiber level, wait.for(loopedFunction) level and the wait.for the callback function level. (Hope I that was formulated understandable)
So my questions is: Do you guys have a good approach where you can loop throw callback functions and can use results of those whenever you like?
good = easy to use, read, performant, not recursive,...
(Im sorry if this question is stupid, but I really have problems getting along with this asynchronous programming)
If you want to wait for doSthWith to finish before doing the same but with the nex url, you have to chain your promises and you can use array.prototype.reduce to do that:
urls = ["aaa", "bbb", "ccc", "ddd"];
urls.reduce((lastPromise, url) => lastPromise.then((resultOfPreviousPromise) => {
console.log("Result of previous request: ", resultOfPreviousPromise); // <-- Result of the previous request that you can use for the next request
return doSthWith(url);
}), Promise.resolve());
function doSthWith(arg) { // Simulate the doSthWith promise
console.log("do something with: ", arg);
return new Promise(resolve => {
setTimeout(() => resolve("result of " + arg), 2000);
});
}
Use async, specifically async.each:
const async = require('async');
function doSthWith(url, cb) {
console.log('doing something with ' + url);
setTimeout(() => cb(), 2000);
}
const urls = ['https://stackoverflow.com/', 'https://phihag.de/'];
async.each(urls, doSthWith, (err) => {
if (err) {
// In practice, likely a callback or throw here
console.error(err);
} else {
console.log('done!');
}
});
Use async.map if you are interested in the result.
When I need to loop over promises I use my handy dandy ploop function. Here is an example:
// Function that returns a promise
var searchForNumber = function(number) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
var min = 1;
var max = 10;
var val = Math.floor(Math.random()*(max-min+1)+min);
console.log('Value is: ' + val.toString());
return resolve(val);
}, 1000);
});
};
// fn : function that should return a promise.
// args : the arguments that should be passed to fn.
// donefn : function that should check the result of the promise
// and return true to indicate whether ploop should stop or not.
var ploop = function(fn, args, donefn) {
return Promise.resolve(true)
.then(function() {
return(fn.apply(null, args));
})
.then(function(result) {
var finished = donefn(result);
if(finished === true){
return result;
} else {
return ploop(fn, args, donefn);
}
});
};
var searchFor = 4;
var donefn = function(result) {
return result === searchFor;
};
console.log('Searching for: ' + searchFor);
ploop(searchForNumber, [searchFor], donefn)
.then(function(val) {
console.log('Finally found! ' + val.toString());
process.exit(0);
})
.catch(function(err) {
process.exit(1);
});
This code calls a function (getTable()) that returns a promise:
function getTables() {
while (mLobby.tblCount() < 4) {
getTable().then(function(response) {
mLobby.addTable(response);
}, function (error) {
console.error("getTable() finished with an error: " + error);
});
}
}
It never resolves (and eventually crashes due to full memory) because of the clash of the async function call and the normal flow of the while loop. I tried changing the while to an if with a recursive call, but that gave the same result:
function getTables() {
if (mLobby.tblCount() < 4) {
getTable().then(function(response) {
mLobby.addTable(response);
getTables();
}
});
}
In my experience, using Promises inside of a synchronous action like while won't work like you want.
What I've done is use async await to accomplish the same task. Something like...
async function getTables() {
while (mLobby.tblCount() < 4) {
await getTable();
// whatever other code you need...
}
}
So, the while loop will continue to work as expected only after each getTable() call is resolved. Definitely test this code, obviously.
Here's a really simple working example of what I'm talking about: https://codepen.io/alexmacarthur/pen/RLwWNo?editors=1011
Is there a particular reason to do this in a while loop and adding the results to the lobby object while doing it?
Maybe you could use just a standard-for-loop that calls getTable 4 times:
function getTables(limit=4){
let results = [];
for(let i=0; i<limit;i++){
results.push(getTable());
}
return Promise.all(results);
}
Your method will return a promise that will resolve with the array of the result of the given getTable-calls
getTables().then(tables => {
tables.forEach(table => {
if(myLobby.tableCount() < 4) myLobby.addTable(table)
})
}).catch(console.warn)
I don't have a lot of experience with JavaScript closures nor AngularJS promises. So, here is my scenario
Goal:
I need to make $http requests calls within a for loop
(Obvious) problem
Even though the loop is done, my variables still have not been updated
Current implementation
function getColumns(fieldParameters)
{
return $http.get("api/fields", { params: fieldParameters });
}
for(var i = 0; i < $scope.model.Fields.length; i++)
{
var current = $scope.model.Fields[i];
(function(current){
fieldParameters.uid = $scope.model.Uid;
fieldParameters.type = "Columns";
fieldParameters.tableId = current.Value.Uid;
var promise = getColumns(fieldParameters);
promise.then(function(response){
current.Value.Columns = response.data;
}, error);
})(current);
}
//at this point current.Value.Columns should be filled with the response. However
//it's still empty
What can I do to achieve this?
Thanks
If I understand your question correctly, you have a list of fields that you need to do some work on. Then when all of that async work is done, you want to continue. So using the $q.all() should do the trick. It will resolve when all of the list of promises handed to it resolve. So it's essentially like "wait until all of this stuff finishes, then do this"
You could try something like this:
var promises = [];
for(var i=0; i< $scope.model.Fields.length; i++) {
var current = $scope.model.Fields[i];
promises.push(getColumns(fieldParameters).then(function(response) {
current.Value.Columns = response.data;
}));
}
return $q.all(promises).then(function() {
// This is when all of your promises are completed.
// So check your $scope.model.Fields here.
});
EDIT:
Try this since you are not seeing the right item updated. Update your getColumns method to accept the field, the send the field in the getColumns call:
function getColumns(fieldParameters, field)
{
return $http.get("api/fields", { params: fieldParameters}).then(function(response) {
field.Value.Columns = response.data;
});
}
...
promises.push(getColumns(fieldParameters, $scope.model.Fields[i])...
var promises = [];
for(var i = 0; i < $scope.model.Fields.length; i++)
{
var current = $scope.model.Fields[i];
promises.push(function(current){
//blahblah
return promise
});
}
$q.all(promises).then(function(){
/// everything has finished all variables updated
});
I'm a little confused how to determine when async function called multiple times from another one is finished a call from the last iteration:
function MainAsyncFunction(callback) {
for (var i = 0; i < 10; i++) {
SubAsyncFunction(function(success) {
if (i >= 10 && success) { // THIS IS WRONG?!
callback(true); // happens too early
}
});
}
};
function SubAsyncFunction(callback) {
SubSubAsyncFunction(function() {
callback(true);
});
}
What I'm doing is calling the Google Distance Matrix service, which has a limitation of 25 destinations, hence I'm having to split my array of destinations to call this service multiple times but I don't understand when it's finished.
and in the main bit of code I can tell that the second iteration of the loop in the MainAsyncFunction hasn't yet completed when it does a call back.
I think my problem is I haven't got my head around the order of events when dealing with Async functions in JavaScript... please explain how the subject is normally achieved.
You could use the jQuery Deferred object, which acts as a token representing the status of an async operation.
The following is a simplified example:
//set up your sub method so that it returns a Deferred object
function doSomethingAsync() {
var token = $.Deferred();
myAsyncMethodThatTakesACallback(function() {
//resolve the token once the async operation is complete
token.resolve();
});
return token.promise();
};
//then keep a record of the tokens from the main function
function doSomethingAfterAllSubTasks() {
var tokens = [];
for (var i=0; i < 100; i++) {
//store all the returned tokens
tokens.push(doSomethingAsync());
}
$.when.apply($,tokens)
.then(function() {
//once ALL the sub operations are completed, this callback will be invoked
alert("all async calls completed");
});
};
The following is an updated version of the OP's updated code:
function MainAsyncFunction(callback) {
var subFunctionTokens = [];
for (var i = 0; i < 10; i++) {
subFunctionTokens.push(SubAsyncFunction());
}
$.when.apply($,subFunctionTokens)
.then(function() {
callback(true);
});
};
function SubAsyncFunction() {
var token = $.Deferred();
SubSubAsyncFunction(function() {
token.resolve();
});
return token.promise();
};
Perhaps the ajaxStop() event? This is a jQuery event that only fires when all active AJAX requests are completed.
The problem is that the value of i is constantly changing in the loop, finally being out of bounds after failing the loop conditional.
The easiest way to fix this is:
for( i=0; i<5; i++) { // or whatever your loop is
(function(i) {
// the value of i is now "anchored" in this block.
})(i);
}
Hey all. I have, what appears to be, a trivial problem. I have the following JavaScript:
$(function() {
var r = GetResults();
for(var i = 0; i < r.length; i++) {
// Do stuff with r
}
});
function GetResults() {
$.getJSON("/controller/method/", null, function(data) {
return data;
});
}
Due to the fact that I'm calling a method asynchronously, the script continues executing and when it encounters the for loop, r obviously isn't going to have a value yet. My question is: when I have a method that is doing an asynchronous operation, and I'm dependent on the data it returns back in the main block, how do I halt execution until the data is returned? Something like:
var r = GetResults(param, function() {
});
where the function is a callback function. I cannot move the for loop processing into the callback function of the JSON request because I am reusing the functionality of GetResults several time throughout the page, unless I want to duplicate the code. Any ideas?
move your "do stuff with r" block into your $.getJSON callback. you can't do stuff with r until it has been delivered, and the first opportunity you'll have to use r is in the callback... so do it then.
$(function() {
var r = GetResults();
});
function GetResults() {
$.getJSON("/controller/method/", null, function(data) {
for(var i = 0; i < data.length; i++) {
// Do stuff with data
}
return data;
});
}
I've run into something similar before. You'll have to run the ajax call synchronously.
Here is my working example:
$.ajax({
type: "POST",
url: "/services/GetResources",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: '{resourceFileName:"mapedit",culture:"' + $("#lang-name").val() + '"}',
cache: true,
async: false, // to set local variable
success: function(data) {
localizations = data.d;
}
});
Ajax already gives you a callback, you are supposed to use it:
function dostuff( data ) {
for(var i = 0; i < data.length; i++) {
// Do stuff with data
}
};
$(document).ready( function() {
$.getJSON( "/controller/method/", null, dostuff );
});
You could do this:
$(function() {
PerformCall();
});
function PerformCall() {
$.getJSON("/controller/method/", null, function(data) {
for(var i = 0; i < data.length; i++) {
// Do stuff with data
}
});
}
The short answer is that you can't block on an asynchronous operation...which is of course, the meaning of "asynchronous".
Instead, you need to change your code to use a callback to trigger the action based on the data returned from the $.getJSON(...) call. Something like the following should work:
$(function() {
GetResults();
});
function GetResults() {
$.getJSON("/controller/method/", null, function(data) {
for(var i = 0; i < data.length; i++) {
// Do stuff with data
}
});
}
Given your updated requirements ...
I cannot move the for loop processing
into the callback function of the JSON
request because I am reusing the
functionality of GetResults several
time throughout the page, unless I
want to duplicate the code. Any ideas?
... you could modify GetResults() to accept a function as a parameter, which you would then execute as your $.getJSON callback (air code warning):
$(function() {
GetResults(function(data) {
for(var i = 0; i < data.length; i++) {
// Do stuff with data
}
});
});
function GetResults(callback) {
$.getJSON("/controller/method/", null, callback);
}
As you can see from the general tide of answers, you're best off not trying to fight the asynchronous jQuery programming model. :)
This is not possible.
Either you make your function synchronous or you change the design of your code to support the asynchronous operation.
You can have a callback with parameters that should work nicely...
$(function() {
GetResults(function(data) {
for(var i = 0; i < data.length; i++) {
// Do stuff with data
}
});
});
function GetResults(func) {
$.getJSON("/controller/method/", null, func);
}
Move the data processing into the callback:
$(function() {
GetResults();
});
function GetResults() {
$.getJSON("/controller/method/", null, function(data) {
for(var i = 0; i < data.length; i++) {
// Do stuff with data
}
});
}