Handling multiple asynchronous requests in Azure? - javascript

For the reading a table script shown below, I am trying to run the query in the parameter and for each result trying to append a value from a different table to the result. But due to asynchronous nature of azure, request.respond() is always called before getInvites. Which means that the results are never appended with invites.
function read(query, user, request) {
request.execute({
success: function (results) {
for (var i = 0; i < results.length; i++) {
getInvites(results[i].id, function (invites) {
console.log("Assigning results.invites"); //runs second
results[i].invites = invites;
});
}
console.log("Request Responding"); //runs first
request.respond();
}
});
}
function getInvites(id, cb) {
var InvitesTable = tables.getTable("Invites").where({
"PlanID": id
}).select("UserID", "Attending");
InvitesTable.read({
success: function (results) {
if (cb) cb(results);
}
});
}
This is a followup question from this as I am unable to use external libraries in Azure. So how can I workaround the problem?

The asynchronous nature of the operations makes that a little challenging. What you need to do is to only call request.respond() once all the operations are done. I really miss the await keyword which I'm so fond from C#, but I've used an "asynchronous for loop" before, and it's worked quite well. Your code would look something like the one shown below:
function read(query, user, request) {
request.execute({
success: function (results) {
var index = 0;
var executeStep = function() {
if (index === results.length) {
// all invites have been retrieved
console.log("Request Responding");
request.respond();
} else {
getInvites(results[index].id, function(invites) {
console.log('Assigning results.invites');
results[i].invites = invites;
index++;
executeStep();
});
}
}
executeStep();
}
});
}
function getInvites(id, cb) {
var InvitesTable = tables.getTable("Invites").where({
"PlanID": id
}).select("UserID", "Attending");
InvitesTable.read({
success: function (results) {
if (cb) cb(results);
}
});
}

Ok you might do something like this
function read(query, user, request) {
request.execute({
success: function (results) {
for (var i = 0, len = results.length; i < len; i++) {
getInvites(results.id, function (invites) {
console.log("Assigning results.invites"); //runs second
results.invites = invites;
if (i === len-1) request.respond();
});
}
console.log("Request Responding"); //runs first
}
});
}
function getInvites(id, cb) {
var InvitesTable = tables.getTable("Invites").where({
"PlanID": id
}).select("UserID", "Attending");
InvitesTable.read({
success: function (results) {
if (cb) cb(results);
}
});
}
It basically means that request.respond() will be called at your successful callback of the last iteration.

Related

Calling second request after first one is finished

I have a question about requests.I make first request then I suppose first onSuccess method will be runnning but program make second request immediately.How can i handle it? Context.getAlarmGroupById is called 2 times immediately.
my code:
function loadAll () {
Context.getAlarmGroupChainById({
id: $stateParams.id
}, onSuccess, onError);
function onSuccess(data, headers) {
vm.temp=data;
var numberGroupChain=vm.temp.length;
for(var i=0; i<numberGroupChain; i++){
vm.location.push(vm.temp[i].location);
vm.alarmChainList.push({name: vm.location[i],active:null,totalAlarmGroupNumber:null});
//loadData(vm.temp[i].id);
asyncLoop(vm.temp[i].id);
}
}
function onError(error) {
AlertService.error(error.data.message);
}
var index = 0;
function asyncLoop(chainId) {
if(index >= vm.temp.length) {
return;
}
Context.getAlarmGroupById({
id:chainId
}, onSuccess, onError);
function onSuccess(data,headers){
index++;
asyncLoop();
}
function onError(data,headers){
index++;
asyncLoop();
}
}
}
Firstly: Car.getGroupId is an asynchronous function. So you have to ensure that the previous call is completed, before the next call.
Secondly: The recursive function is created for replacing the loop, after the success function and index increment, the recursive function is called to ensure your required requirement.
Third: Change the calling sequence of asyncLoop(vm.temp[i].id); after loop:
Finally:
Please use the following code:
function loadAll () {
var index = 0;
function asyncLoop(chainId) {
if(index >= vm.temp.length) {
return;
}
Context.getAlarmGroupById({
id:vm.temp[index].id
}, onSuccess, onError);
function onSuccess(data,headers){
index++;
asyncLoop();
}
function onError(data,headers){
index++;
asyncLoop();
}
}
function onSuccessParent(data, headers) {
vm.temp=data;
var numberGroupChain=vm.temp.length;
for(var i=0; i<numberGroupChain; i++){
vm.location.push(vm.temp[i].location);
vm.alarmChainList.push({name: vm.location[i],active:null,totalAlarmGroupNumber:null});
//loadData(vm.temp[i].id);
}
asyncLoop();
}
function onErrorParent(error) {
AlertService.error(error.data.message);
}
Context.getAlarmGroupChainById({
id: $stateParams.id
}, onSuccessParent, onErrorParent);
}
Here is the pure JavaScript way to do this and it works perfectly fine. Use Promise.then(). Do like below:
var request1 = new Promise(function(resolve, reject) {
resolve('Success!');
});
request1.then(function(value) {
// make request2 here
});

How to stop $.ajax within for loop?

I'm running into an issue where my api call reaches it's timeout limit, but continues to loop for the remainder of the requests provided resulting in n number of timeout logs in the console (In this case 5). I want it so that I can do something along the lines of a break; and just exit entirely so the remaining calls don't get logged. E.g. If the call immediately times out, only one timeout log will be logged instead of the current 5 and none of the five api requests will be made.
let qs = {
requests: 5,
timeout: 1000
};
let prices = [];
let highest = 0;
const url = 'http://somedata.com';
function xhr(qs){
return $.ajax({
url: url,
dataType: 'json',
timeout: qs.timeout,
success: function (data) {
let json = JSON.stringify(data['price']);
prices.push(json);
getHighestPrice(prices);
console.log(highest);
},
error: function(e, textstatus, message) {
if(textstatus==="timeout") {
console.error(textstatus);
} else {
console.error(textstatus);
}
}
});
}
function makeRequest(qs) {
for(let i = 0; i < qs.requests; i++) {
xhr(qs);
}
}
function getHighestPrice(arr) {
for(let i = 0; i <= arr.length; i++) {
if (arr[i] > highest) {
highest = arr[i]
}
}
return highest;
}
makeRequest(qs);
Your code makes all the requests at once
It should be noted that this code will stop the "chaining" once any error occurs in $.ajax, not just timeout - if that's not the required behaviour, there is a little more to do
To make the call only if the previous is successful, you can chain the promises returned by $.ajax
let qs = {
requests: 5,
timeout: 1000
};
let prices = [];
let highest = 0;
function xhr(qs){
return $.ajax({
url: url,
dataType: 'json',
timeout: qs.timeout,
success: function (data) {
let json = JSON.stringify(data['price']);
prices.push(json);
getHighestPrice(prices);
console.log(highest);
},
error: function(e, textstatus, message) {
if (textstatus==="timeout") {
console.error(textstatus);
} else {
console.error(textstatus);
}
}
});
}
function makeRequest(qs) {
let p = $.when();
for(let i = 0; i < qs.requests; i++) {
p = p.then(() => xhr(qs));
}
}
as others have pointed out you don't need to pass qs to xhr, however, I'm assuming the code you posted may be simplified so have not removed the qs argument
An alternative would be
let qs = {
requests: 5,
timeout: 1000
};
let prices = [];
let highest = 0;
function xhr(qs){
return $.ajax({
url: url,
dataType: 'json',
timeout: qs.timeout
}).then(data => {
let json = JSON.stringify(data['price']);
prices.push(json);
getHighestPrice(prices);
console.log(highest);
});
}
function makeRequest(qs) {
let p = $.when([]);
for(let i = 0; i < qs.requests; i++) {
p = p.then(() => xhr(qs));
// or p.then(xhr); if you don't need to pass qs on to xhr function (remove qs argument in xhr as well)
}
p.then(() => {
// this is run once all have completed
}).fail(reason => {
// this is run if there's a failure anywhere
});
}
Since it is a callback, it will be executed asynchronously. So even if you throw an error from one of the callback you provided, the rest will be executed later or sooner. One of the solution I could think of is to have a flag that will be set to true if one of the AJAX causes an error. Something like:
var hasError = false;
$.ajax({
error: function (e, textstatus, message) {
if (textstatus === "timeout") {
if (!hasError) console.error(textstatus);
hasError = true;
}
}
});
Using Promise.all() can simplify this use case. If you cannot use promises try throwing an exception from the error handler. Like so:
$.ajax({
error: function (e, textstatus, message) {
if (textstatus === "timeout") throw e
}
})
Be sure to catch the the exception:
function makeRequest(qs) {
try {
for(let i = 0; i < qs.requests; i++) {
xhr(qs);
}
} catch (e) { // Handle error here }
}
To get the desired behavior you will have to make all of the calls sequentially i.e. you can't start the next call until the previous one has finished (otherwise you won't know if it has failed or not).
You could use the done callback to determine whether the next call should be made:
function makeRequest(i) {
xhr().done(function(){
if (i < qs.requests){
makeRequest(i+1)
}
})
}
makeRequest(0); // Kick things off here
Also, you don't need to pass the qs variable into the makeRequest or xhr functions. It doesn't change throughout the calls so just use it as-is within the xhr function without passing it around.

Use callback with javascript

I am creating project using javascript and nodejs. I am integrating callback in my function inside for loop with condition basis,but am unable to do this.my problem is callback is completed on first iteration of loop. here is my code:
function tagEndpointNames(callback) {
var data = userGenerateToken();
var sql = "SELECT * FROM topology_data WHERE topology_coordinates !='' and topology_uuid is not null"
var query = conn.query(sql, function(err, tagEndpointNames) {
for (var i = 0; i < tagEndpointNames.length; i++) {
var topologytagData = {
"topology_tag": tagEndpointNames[i].topology_uuid
}
var tpCooridinates = JSON.parse(tagEndpointNames[i].topology_coordinates);
for (var j = 0; j < tpCooridinates.stageObjects.length; j++) {
if (tpCooridinates.stageObjects.length) {
if (tpCooridinates.stageObjects[j].endPointId) {
if (isGuid(tpCooridinates.stageObjects[j].endPointId)) {
var endPointUUID = tpCooridinates.stageObjects[j].endPointId;
var _ro = require('request');
var url = url;
var _d = '';
_ro({
url: url,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + data['access_token']
},
json: topologytagData
}, function(_e, _r, _b) {
if (_r.statusCode == 200 && !_e) {
callback()
//return;
} else {
callback()
console.log("andarss")
return;
}
})
}
}
}
}
}
})
}
Here is the function call:
tagEndpointNames(function(){
console.log ('Server Closed during MIGRATION JOB 4');
server.close(function () {
process.exit(0);
});
})
When you are running asynchronous process with callback in a for loop, remember that the callback from callee will be fired in the first event completed inside the loop. In your case request lib call is an asynchronous process inside for loop, you need to handle all callback from all the request call before you want to callback the callee.
Please read:
How to write asynchronous functions for Node.js
Maybe it's time for you to start using Javascript Promise.
The async library for Node will help you for doing this kind of tasks.
Use async waterfall.It Runs an array of functions in series, each passing their results to the next in the array. However, if any of the functions pass an error to the callback, the next function is not executed and the main callback is immediately called with the error.
js
var create = function (req, res) {
async.waterfall([
_function1(req),
_function2,
_function3
], function (error, success) {
if (error) { alert('Something is wrong!'); }
return alert('Done!');
});
};
function _function1 (req) {
return function (callback) {
var something = req.body;
callback (null, something);
}
}
function _function2 (something, callback) {
return function (callback) {
var somethingelse = function () { // do something here };
callback (err, somethingelse);
}
}
function _function3 (something, callback) {
return function (callback) {
var somethingmore = function () { // do something here };
callback (err, somethingmore);
}
}
Reference

Javascript for loop Promises

I have an array of urls like this
var urls = ["www.google.com", "www.yahoo.com"];
And I want to loop though the urls and perform an async task inside the loop and not move on to the next item until the async task has finished. I know you can do this with promises but I have having some trouble with it. Here what I have
var xmlReader = require('cloud/xmlreader.js');
function readResponse_async(xlmString) {
var promise = new Parse.Promise();
xmlReader.read(xlmString, function (err, res) {
if(err) {
promise.reject(err);
} else {
promise.resolve(res);
}
});
return promise;
}
for (i = 0; i < urls.length; i++) {
Parse.Cloud.httpRequest({
url: unionUrls[i],
}).then(function(httpResponse) {
try {
// console.log(httpResponse.text)
return readResponse_async(httpResponse.text)
} catch (e) {console.log(e)}
}
But right now it doesn't wait for the readResponse_async to finish, how can I have it wait for that?
Thanks
EDIT
After reading the response I make a save to my database and I have another array like this
var location = ['USA', 'England'];
And I make the save like this
function saveLoc_async(data, location) {
var i3, i4, i5, m,
TestItem = Parse.Object.extend("TestItem"),//can be reused within the loops?
promise = Parse.Promise.as();//resolved promise to start a long .then() chain
for (i3 = 0; i3 < data.count(); i3++) {
(function(testItem) {
testItem.set("item", data.at(i));
testItem.set("location", location);
//build the .then() chain
promise = promise.then(function() {
return testItem.save();
});
})(new TestItem());
//************************
//CALL retry(); here?
//**************************
}
Because with your answer I have
function retry() {
if (urlsUnion.length > 0) {
var nextUrl = urlsUnion.pop();
//********** ADDED LINE
var nextLoc = location.pop();
Parse.Cloud.httpRequest({
url: nextUrl,
}).then(function(httpResponse) {
xmlReader.read(httpResponse.text, function (err, res) {
if(err) {
// show an error
} else {
//********** ADDED LINE
saveLoc_async(res, nextLoc);
retry();
}
});
});
}
}
SO where should retry(); go because right now with the save sometimes it puts the second location with one of the first items url? why would that happen?
I did something similar to this for an animation.
var actions = [drawXXX, fadeOutYYY, drawXYZ];
this.startAnimation = function () {
actions.reduce(function (previousAction, nextAction) {
return previousAction.then(nextAction)
}, $.when());
}
Your code fires both urls immediately, and does not wait in-between.
What you would have to do is to remove the first url from the array and fire it. In the 'then' branch check if you still have url's in the array and repeat.
Like this (untested, edited to make the code clean again):
var xmlReader = require('cloud/xmlreader.js');
function readResponse_async(xlmString) {
xmlReader.read(xlmString, function (err, res) {
if(err) {
// show an error
} else {
readFirstUrl();
}
});
}
function readFirstUrl() {
if (urlsUnion.length == 0) {
return;
}
var url = urlsUnion.pop();
Parse.Cloud.httpRequest({
url: url,
}).then(function(httpResponse) {
readResponse_async(httpResponse.text);
});
}
readFirstUrl();
Not sure I understand your use of unionUrls array, but if you have your URL's in a urls array, I think this is pretty clean:
function getUrl(url) {
return Parse.Cloud.httpRequest(url)
.then( function(httpResponse) {
return readResponse_async(httpResponse.text);
});
}
urls.reduce( function(prev, url) {
return prev ? prev.then( function() { getUrl(url); }) : getUrl(url);
}, null);

Return function value AFTER for-loop ends

I have the following function and I'm getting in the console false then true in that order. The true comes like 1-2 seconds after the false. I need the function to return false ONLY if no files were uploaded.
function uploadFeaturedImg()
{
var uploaded = false,
files = featuredImg.files;
if (files.length > 0)
{
for (var i = 0, len = files.length; i < len; i++)
{
var params = {
Key: 'tournament/image/'+files[i].name,
ContentType: files[i].type,
Body: files[i]
};
bucket.upload(params, function(err, data)
{
if (!err)
{
if (!uploaded) uploaded = true;
console.log(uploaded);
}
else
{
fileAlert(files[i].name);
}
});
}
}
console.log(uploaded);
return uploaded;
};
I ended up using another approach that worked for me better.
I'm also using .ajaxStop(): https://api.jquery.com/ajaxStop/. This basically lets me know when all files have been uploaded (if any).
There is an asynchrone problem here:
When you do your loop, you call for each iteration the function bucket.upload with a callback (it will be call when the action is finished). But your loop is ended when the last call yo bucket.upload is done, but NOT when all the callback are done.
So you return line is called BEFORE all the callback.
If you can understand that, you can also understand an asyncron function never return something (cause the function have to wait for something before end) but call a callback function when all it's done. (in this case the callback param)
To work fine you have to use a lib like async (Doc here)
Use it like that :
if (files.length > 0) {
async.each(
files,
function(file, cb) {
// This function will be called for each iteration
// ...
// Call your funciton here
bucket.upload(params, function(err, data) {
// When the callback is done, call the end for the iteration
cb();
}
},
function(err) {
// This function is called only when ALL callback are done.
callback(true); // All upload are done correctly
});
);
}
else {
// No files to upload
callback(false);
}
Well, it is always a bit complicated to deal with asynchronous code.
You should change your approach and pass a callback function in the signature of your method:
function uploadFeaturedImg(callback)
{
var uploaded = false,
files = featuredImg.files;
if (files.length > 0)
{
for (var i = 0, len = files.length; i < len; i++)
{
var params = {
Key: 'tournament/image/'+files[i].name,
ContentType: files[i].type,
Body: files[i]
};
bucket.upload(params, function(err, data)
{
if (!err)
{
if (!uploaded) uploaded = true;
if(callback) callback(); //Do what you were supposed to do with the result of your function in this callback function
}
else
{
fileAlert(files[i].name);
}
});
}
}
return uploaded;
};

Categories

Resources