Angular making pseudo-synchronous HTTP requests - javascript

I want to construct a mechanism that would access a database via POST requests. So far, I do received the desired data, but am have issues with the timing. Here are three pieces of code that I'm using (simplified to keep the focus of the question).
First, a factory handling the HTTP request vis-à-vis a servlet:
var My_Web = angular.module('My_Web');
My_Web.factory('DB_Services', function($http , $q) {
var l_Result ;
var DB_Services = function(p_Query) {
var deferred = $q.defer();
var url = "http://localhost:8080/demo/servlets/servlet/Test_ui?";
var params = "data=" + p_Query ;
var Sending = url + params ;
$http.post(Sending).
success(function(data, status, headers, config) {
deferred.resolve(data);
}).
error(function(data, status, headers, config) {
deferred.reject(status);
});
return deferred.promise;
}
return DB_Services;
});
Second, a general purpose function handling the promise (or so I meant) exposed to all the controllers that would need to extract data from the remote DB:
$rootScope.Get_Data_from_DB = function(p_Query) {
DB_Services(p_Query).then(function(d) {
console.log("In Get_Data_from_DB; Received data is: " + JSON.stringify(d));
$scope.data = d;
});
};
Third, one example within one of the controllers:
$scope.Search_Lookups = function () {
console.log ("search for lookup data...") ;
var l_Lookup_Type = document.getElementById("THISONE").value ;
var l_Send_Request_Data = '{"Requestor_ID":"4321" , "Request_Details" : { "Data_type" : "' + l_Lookup_Type + '" } }' ;
console.log("Sending Request: " + l_Send_Request_Data) ;
l_Data = $rootScope.Get_Data_from_DB(p_Query) ;
console.log ("Received response: " + l_Data) ;
Deploy_data(l_Data) ;
}
The function Deploy_data(l_Data) is responsible of dismembering the received data and put the relevant pieces on screen.
What happens is that I get on the console the string Received response: undefined and immediately after the result of the retrieval as In Get_Data_from_DB; Received data is: (here I get the data).
The Received response: undefined is printed from the invoking function (third piece of code), whereas the output with the actual data is received and printed from within the second piece of code above. This means that the invocation to Deploy_data would not receive the extracted data.
Once again, the same mechanism (i.e. the factory $rootScope.Get_Data_from_DB) would be vastly used by many controllers.
I thought of using $scope.$watch but I'm not sure because the same user might be triggering several queries at the same time (e.g. request a report that might take few seconds to arrive and, in the meantime, ask for something else).

I think I found a solution (at least it appears to be ok for the time being). The global function Get_Data_from_DB accepts a second parameter which is a callback of the invoking controller.
The invoking controller creates a private instance of the Get_Data_from_DB function and triggers a request providing the callback function.
I'll need to test this with parallel queries, but that is still a long way to go...

Related

AWS Lambda Node.js HTTP GET Request never executed

I have the following intent. When intent is entered I want to perform a GET request to an external API. The intent is entered, however my http.get requested is not. If you look below I added a log statement within the request and it is never executed. Any ideas what the problem may be?
'BlogEntrySlugIntent': function () {
var url = 'http://jsonplaceholder.typicode.com/posts/1';
http.get( url, function( response ) {
console.log('In get request');
response.on( 'data', function( data ) {
var text = 'Data returned is: ' + data;
this.emit(':tell', 'hellloooo');
});
});
},

AngularJS and $http get JSON - data is not defined

Hi I am trying to retrieve some data from webservice using AngularJS $http get.
I have the following code snippet:
In the servicesjs:
.factory('BoothDesignatedCoordsService', ['$http', function ($http) {
var factory = {};
factory.getBoothDesignatedCoords = function (strBoothName, intFloorPlanID) {
var sendToWS;
var boothDesignatedCoords
var JSONObj = {
BoothName: strBoothName,
FloorPlanID: intFloorPlanID
};
sendToWS = JSON.stringify(JSONObj)
var urlBase = 'http://localhost:4951/wsIPS.asmx/fnGetBoothDesignatedCoords?objJSONRequest=' + sendToWS;
return $http.get(urlBase)
}
return factory;
}])
In the controllerjs:
var boothDesignatedCoords = BoothDesignatedCoordsService.getBoothDesignatedCoords(strListShortListedBooth[i], 3).success(function (response, data, status) {
console.log("successfully send booth name and floor plan id to ws");
console.log("data " + data + " , status : " + status);
console.log("data " + data);
boothDesignatedCoords = data;
for (var c = 0; c < boothDesignatedCoords.length; c += 2) {
}
The $http get is successful as I am able to print "successfully send booth name and floor plan id to ws" in the browser console log.
When I tried to print console.log("data " + data), it gives me some values of an integer array. That is exactly what I want. But in the controller I tried to assign data to the variable boothDesignatedCoords, the program does not run the for loop. Am I missing some code?
EDIT:
I tried to trace the code ( trace the variable called "data" in the controllerjs) and it says "data is not defined"
You appear to be confused about the methods available on the $http promise and their arguments. Try this
BoothDesignatedCoordsService.getBoothDesignatedCoords(strListShortListedBooth[i], 3)
.then(function(response) {
var data = response.data
var status = response.status
console.log('data', data) // note, no string concatenation
// and so on...
})
FYI, the success and error methods have been deprecated for some time and removed from v1.6.0 onwards. Don't use them.
I also highly recommend passing query parameters via the params config object
var urlBase = 'http://localhost:4951/wsIPS.asmx/fnGetBoothDesignatedCoords'
return $http.get(urlBase, {
params: { objJSONRequest: sendToWS }
})
This will ensure the key and value are correctly encoded.

How to handle response to several asynchronous request (AJAX calls) made in a loop

I need to collect logs for different devices from a backend, to export it to a csv-file. Problem is the number of devices can vary. So I ask the backend for the amount of devices and loop through the requests for the logs.
The problem is the for... loop runs too faster than I get the responses to $.post, so the loop finishes before I get the responses. After some research I could handle that behaviour, but now I was asked to add data to the request, for which I have no reference to the according device is stored. So I added the device names and spots I need to poll in an external js-file, so I have a defined list to loop through.
I tried to use the index of the for loop to fetch the device names, which didn't work, since the loop was too fast. Now I have created a workaround, by defining a counter and pushing the devices in another variable. This doesn't feel "clean" and there should be a better way to poll the data and keep track for which device it is.
The code so far:
function collectData() {
var outString = "";
var lots of stuff I can pre-fetch
var logs = function (outString, saveCSV) {
var postString;
var devices = [];
var count = 0;
for (i = 1; i <= maxDevice; i++) {
postString = build postString in loop
devices.push(spots[0][i - 1]);
$.post('/path/foobar.db',
postString,
function (data) {
outString += "Spotlist for: " + spots[0][count] + "\n";
count++;
outString += data.replace(/{/g, "").replace(/}/g, "").replace(/:/g, ";").replace(/,/g, "\n").replace(/\"/g, "");
outString += "\n\n";
});
postString = "/path/eventlog.csv?device=" + i;
$.get(postString,
function (data) {
outString += "Event Log: \n" + data + "\n";
});
postString = "/path/errorlog.csv?device=" + i;
$.get(postString,
function (data) {
outString += "Error Log: \n" + data + "\n";
});
}
$(document).ajaxStop(function () {
saveCSV(outString, filename);
$(this).unbind('ajaxStop');
});
};
var saveCSV = function (outString, filename) {
var tempString = "data:text/csv;charset=utf-8," + outString;
var encodedUri = encodeURI(tempString);
var a = document.getElementById("dlLink");
if (window.navigator.msSaveOrOpenBlob) {
blobObject = new Blob([outString], {type: 'text/csv;charset=utf-8'});
window.navigator.msSaveBlob(blobObject, filename);
} else
{
a.setAttribute("href", encodedUri);
a.setAttribute("download", filename);
a.click();
}
};
outString = lots of predefined and pre-fetched stuff
outString += "Device data: \n\n";
logs(outString, saveCSV);
}
The part which I am not satisfied with is:
for (i = 1; i <= maxDevice; i++) {
postString = "get = {" + i + ":en:[";
for (j = 0; j < spots[i].length; j++) {
postString += '"' + spots[i][j] + '",';
}
postString = postString.slice(0, -1) + "]}";
devices.push(spots[0][i - 1]);
$.post('/path/foobar.db',
postString,
function (data) {
outString += "Spotlist for: " + spots[0][count] + "\n";
count++;
outString += data.replace(/{/g, "").replace(/}/g, "").replace(/:/g, ";").replace(/,/g, "\n").replace(/\"/g, "");
outString += "\n\n";
});
To output the device that I collected the spots for I use the counter, to track device names. I have the hunch this is not the best and "cleanest" method, so I'd like to ask if there is any better way to deal with the asynchronity (sp?) in terms of collecting the right device for which the post is made and also to trigger the DL if everything is done.
Since my question doesn't seem to be clear, perhaps I need to narrow it down. The code works, but it seems just to be tinkered by me and there should be cleaner ways to
A) handle the posts/gets, since the outstring for CSV is just put together in the way the requests are answered, so not device 1 is the first in the csv, but the one which comes first. $(document).ajaxStop waits for everything to be finished, but not to be finished in the right order.
B) I need to relate the index of the for loop to the device I poll the data for. I used additional variables, that I count up to go through an additional array. Is there any better way?
The problem is that you need to run in order the methods that are invoked after you get the response to the AJAX calls.
To do so you must undertand that all jQuery AJAX calls return promises. Instead of passing the code to run as a parameter, you can do the following:
var functionToRunAfterResponse(params) = function(params) {
// do something with params
};
var promise = $.post(/*...*/); // some kind of ajax call
promise.then(functionToRunAfterResponse);
Note that the functionToRunAfterResponse will receive the response data as parameter. I.e. it's equivalent to:
promise.then(function(reponseData) {
functionToRunAfterResponse(responseData);
});
This is how it works for a simple call. See $.then and deferred and promise docs.
If you have to make a bunch of calls, you have to do the following:
store the promises of the AJAX calls, for example in an array
check that all the promises are fulfilled (i.e. that all responses have arrived)
run the code in the required order. I.e. run the promise.then in the right order to get the desired result.
To do so, you can use $.when.
The code structure should be like this (pseudocode):
var promises = [];
// Make the calls, and store the promises
for(/**/) {
promises.push( $.post or some other kind of ajax call );
}
// Ensure that all the responses have arrived
$.when.apply($, promises) // See below note
.then(function() {
for each promise in promises
promise[i].then(function(reponseData) {
// run the necessary code
});
NOTE: Here you've got a deeper explanation, but, basically, as $.when expects a series of promises, like this: $.when(promise1, promise2, ...) and you have an array promises[], you have to use apply to make the call, so that the items in the array are passed as individual parameters.
FINAL NOTES:
1) take into account that AJAX calls can fail. In that case, instead of a resolved promise, you get a failed promise. You should check it. If any of the promises fails, $.when will not work as desired:
The method [when] will resolve its master Deferred as soon as all the Deferreds resolve, or reject the master Deferred as soon as one of the Deferreds is rejected.
2) To handle errors, then has two parameters:
$.when.apply().then(function() { /* success */ }, function() { /* error */ });
3) When you make the calls as I've explained, they are all executed in parallell, and the responses will arrive in any order. I.e. there is no warranty that you'll receive all the responses in the order you made the calls. They can even fail, as I explained in 1) That's why you must use when and run then in order again
4) Working with asynchronous methods is great, but you have the responsibility to check that they didn't fail, and run in the right order the code that you need to run after you get the responses.
I can't understand your question, but the main problem is the asynchronity, right?
Try to call the functions asyncronously (summary version):
// Function that manage the data from ajax
var myResponseFunction = function (data) {
$('#response').html('data: ' + JSON.stringify(data));
};
// Ajax function, like your post with one parameter (add all you need)
function myAJAXFunction(callback) {
$.ajax({
type: 'POST',
url: '/echo/json/',
dataType: 'json',
data: {
json: JSON.stringify({
'foo': 'bar'
})
}
}).done(function(response) {
// Done!! Now is time to manage the answer
callback(response);
}).fail(function (jqXHR, textStatus, errorThrown) {
window.console.error('Error ' + textStatus + ': ' + errorThrown);
});
}
// Usually, this function it's inside "document.ready()".
// To avoid the ajax problem we call the function and "data manage function" as parameter.
for (i = 1; i <= maxDevice; i++) {
myAJAXFunction(myResponseFunction);
}
https://jsfiddle.net/erknrio/my1jLfLr/
This example is in spanish but in this answer you have the code commented in english :).
Sorry for my english :S.

Exposing data from Angular's http get reuqest's private scope

I am an angular novice and am working on an app that gets data from an accounting software and visualises different things using google charts.
Since the api of the accounting software doesn't give me the data the way I need it I have to process it before passing it to the google charts api.
No the problem I ran into is that I can't access the data that is returned inside of the http get request function due to scope I guess. I have tried quite a few things, but nothing seems to work. I feel like there should be an obvious solution to this, but can't put my finger on it.
Would be great if someone can help me with a method to expose http request data to make it usable outside of the http function itself.
Here is a code example:
myApp.controller("dataFetch", ['$http', '$scope',function($http, $scope){
var self = this;
self.project = {};
self.TSproject;
self.TShours;
//PASSING AUTHORIZATION
var config = { headers: { 'Authorization': 'Bearer 1lFASlwgM3QwSyZfJVJPO6776X5wlZtogdg8RN-Lt',} };
//GET DATA
$http.get('https://api.freeagent.com/v2/projects/941562', config).then(function(response) {
//SAVING PROJECT DATA
self.project = {
name: response.data.project.name,
url: response.data.project.url
};
return self.project.url;
}, function(errResponse) {
console.error("Get project request failed");
}).then(function(pUrl) {
return
$http.get('https://api.freeagent.com/v2/timeslips?' + 'from_date=2015-09-21&to_date=2015-09-28' + 'user=' + pUrl, config).then(function(response) {
self.TSproject = response.data.timeslips[0].project;
self.TShours = response.data.timeslips[0].hours;
});
});
//GOOGLE CHARTS
$scope.data1 = {};
$scope.data1.dataTable = new google.visualization.DataTable();
$scope.data1.dataTable.addColumn("string","User")
$scope.data1.dataTable.addColumn("number","Qty")
//INSERTING DATA FROM SERVER HERE
$scope.data1.dataTable.addRow([self.TSproject, self.TShours]);
$scope.data1.title="Daniels Timeslips";
}]);
Thanks a lot!
The problem with your code is that you're trying to initialize your charts even before your GET call is complete.
Even though it looks like $http.get() comes first in your code, it's an Asynchronous operation, so JS interpreter will not wait for that call to complete. It will simply fire the AJAX call using $http.get() and continue with the remaining statements in code i.e Google Charts initialization in your case.
When the response is available, we have promises to be invoked after completion of your AJAX call and that is where you're ideally supposed to initialize your charts as they're dependent on that AJAX call response.
Having said that, you can make this work by simply moving your charts initialization to the callback of your second GET request like below.
$http.get('https://api.freeagent.com/v2/projects/941562', config).then(function(response) {
// removing your code for brewity
}, function(errResponse) {
console.error("Get project request failed");
}).then(function(pUrl) {
$http.get('https://api.freeagent.com/v2/timeslips?' + 'from_date=2015-09-21&to_date=2015-09-28' + 'user=' + pUrl, config).then(function(response) {
self.TSproject = response.data.timeslips[0].project;
self.TShours = response.data.timeslips[0].hours;
//GOOGLE CHARTS
$scope.data1 = {};
$scope.data1.dataTable = new google.visualization.DataTable();
$scope.data1.dataTable.addColumn("string","User")
$scope.data1.dataTable.addColumn("number","Qty")
//INSERTING DATA FROM SERVER HERE
$scope.data1.dataTable.addRow([self.TSproject, self.TShours]);
$scope.data1.title="Daniels Timeslips";
});
});
I think you should create the google chart object inside the last http promise.
//GET DATA
$http.get('https://api.freeagent.com/v2/projects/941562', config).then(function(response) {
//SAVING PROJECT DATA
self.project = {
name: response.data.project.name,
url: response.data.project.url
};
return self.project.url;
}, function(errResponse) {
console.error("Get project request failed");
}).then(function(pUrl) {
return
$http.get('https://api.freeagent.com/v2/timeslips?' + 'from_date=2015-09-21&to_date=2015-09-28' + 'user=' + pUrl, config).then(function(response) {
self.TSproject = response.data.timeslips[0].project;
self.TShours = response.data.timeslips[0].hours;
//GOOGLE CHARTS
$scope.data1 = {};
$scope.data1.dataTable = new google.visualization.DataTable();
$scope.data1.dataTable.addColumn("string","User")
$scope.data1.dataTable.addColumn("number","Qty")
//INSERTING DATA FROM SERVER HERE
$scope.data1.dataTable.addRow([self.TSproject, self.TShours]);
$scope.data1.title="Daniels Timeslips";
});
});
Now you should be able to access in your template {{ data1 }} with all the info.

Angular JS: Storing data on a promise

My problem is that I don't know where to store data I need to access in the final callbacks for a http request. In jQuery I would just do the following
var token = $.get('/some-url', {}, someCallback);
token.oSomeObject = {data: 'some data'};
function someCallback( data, status, token ){
token.oSomeObject.data // 'some data'
}
I use the token to store request specific data.
Now the only way I find to acheive the this in Angular is to store the data in the actual config:
var config = {
url: '/some-url',
method: 'GET',
oSomeObject: { data: 'some data' }
};
$http(config).success(someCallback);
function someCallback( data, status, headers, config ){
config.oSomeObject.data // 'some data'
}
For one this prevents you from using the short-hand calls ($http.get, $http.post) I also find it's much a much more obtrusive way when wrapping the calls in a specific service module.
Is there any other way of doing this?
Updated to clarify
I'm probably just missing something simple here not understanding how to properly use the promise API, but just to be sure we're on the same page let me give you a bit more detail to the issue.
I have 2 files: 1) Controller.js 2) AjaxServices.js (all ajax calls are defined here as methods on a service).
AjaxServices.js looks like this:
app.service('AjaxService', function( $http ){
var self = this;
this.createUser = function( oUser, fSuccessCallback ){
return $http.put('/api/user', {oUser: oUser})
.success(fSuccessCallback);
}
}
Controller.js looks like this:
app.controller('MyController', function( $scope, AjaxServices ){
$scope.saveUser = function( oUser ){
var oPromise = AjaxServices.createUser( oUser, $scope.onUserSaved );
oPromise.oUser = oUser // this is how I solve it in jQuery.ajax. The oPromise
// is then sent as the token object in the onUserSaved
// callback
}
$scope.onUserSaved = function( oStatus, status, headers, config ){
oStatus.id // here is the id of the newly created user
// which I want to now hook on to the oUser local object
}
}
How would you achieve the same thing using the promise API?
UPDATE 2. Some notes on your updated code:
You don't have to pass the callback to the service. This effectivelly kills the purpose of the promise. Let the service to it's job without coupling it to any consumer of it's data.
app.service('AjaxService', function( $http ){
var self = this;
this.createUser = function( oUser ){
return $http.put('/api/user', {oUser: oUser});
}
}
Now the service does't care about the the callbacks, meaning you could attach multiple callbacks to same service $http call. Ok, let's move on to the service data consumer, in this case it's the controller:
app.controller('MyController', function( $scope, AjaxServices ){
$scope.saveUser = function( oUser ){
var oOriginalPromise = AjaxServices.createUser( oUser );
//lets modify the orginal promise to include our oUser
var oModifiedPromise = oOriginalPromise.then(function(response) {
response.oUser = oUser;
return response;
});
//in the code above we've chained original promise with .then,
//modified response object and returned it for next promise to receive
//at the same time, .then created a new promise
//finally, after modifying our response, we can pass it to the desired callback.
//note, that .success and .error are $http specific promise methods,
//in all other cases use .then(fnSuccess, fnError)
oModifiedPromise.then($scope.onUserSaved);
}
$scope.onUserSaved = function(responseWithUser) {
responseWithUser.oUser;
}
}
Of course, it is still a bit awkward, since the oUser could be saved in the controller's scope and could be accessed directly from there in $scope.onUserSaved.
UPDATE. I will clarify my answer. You can chain promises in any scope, anywhere. Here's another example with token being injected by service:
myModule.factory('Service', function($http) {
return new function() {
this.getData = function() {
return $http.get(url).then(function(response) {
response.token = myToken;
return response;
}
}
}
});
You could even extend or wrap $http service and inject tokens on responses without your services knowing about it.
If you do this with all your requests, maybe a $httpInterceptor would be more appropriate. Read more about intercepting http calls here
Original answer
Since $http provides you with promise, you can chain it to another promise which would inject token in response:
myModule.factory('Service', function($http) {
return new function() {
this.getData = function() {
return $http.get(url);
}
}
});
//in controller or other service:
var myToken;
var tokenizedPromise = Service.getData().then(function(response) {
response.token = myToken;
return response;
});
The final consumer of the promise has access to the token as well

Categories