Have been looking into getting callbacks for ajax() responses using $.when I'm still unsure how this works fully but this is what I would like the below to do.
When a user adds a town and country per line, it goes to the url in the .ajax() I get a response and it pushes the array to be usable outside of the .each() loop.
At the moment you will see inside here at jsbin that when the button is pressed firstly the response in console.log is [] then when I press it again the addresses show up. then a 3rd press will add the addresses again which shouldn't happen.
jQuery
var addresses,town;
var arrayLocation = [];
$('button').click(function(){
addresses = function() {
deferred = new $.Deferred();
var arrayOfLines = $('#gps').val().split('\n');
$.each(arrayOfLines, function(index, item) {
town = item.split(',');
$.ajax({
url: 'http://maps.googleapis.com/maps/api/geocode/json?address='+town[0]+'&sensor=false',
dataType: 'json',
success: function (data) {
add = data.results[0].address_components[0].long_name;
lat = data.results[0].geometry.location.lat;
lng = data.results[0].geometry.location.lng;
arrayLocation.push("['"+add+"', "+lat+", "+lng+"]");
console.log("['"+add+"', "+lat+", "+lng+"]");
}
});
});
return arrayLocation;
};
$.when(addresses()).then(function(arrayLocation){
console.log(arrayLocation);
});
});
You are not using $.when correctly. The main problem is that the addresses function returns a bare array. It's true that this array will be populated in the future when an async operation (the set of AJAX calls) completes, but from the point of view of addresses's caller this is impossible to know. Therefore the caller has absolutely no chance of reacting to the operation being completed.
What you would normally do is return the return value of $.ajax to the caller, somewhat like this:
addresses = function() {
return $.ajax({ ... });
};
The caller could then do
$.when(addresses()).then(function(result) { ... });
In this particular example however this is not directly possible because there are multiple AJAX calls being made, so you need some way of "combining" all of them into one package. There are multiple ways to do this, so there is also a matter of what you prefer here.
One solution would be to use an array of AJAX promises:
$('button').click(function(){
var arrayLocation = [];
addresses = function() {
var promises = [];
var arrayOfLines = $('#gps').val().split('\n');
$.each(arrayOfLines, function(index, item) {
town = item.split(',');
promises.push($.ajax({
url: 'http://maps.googleapis.com/...',
dataType: 'json',
success: function (data) {
add = data.results[0].address_components[0].long_name;
lat = data.results[0].geometry.location.lat;
lng = data.results[0].geometry.location.lng;
arrayLocation.push("['"+add+"', "+lat+", "+lng+"]");
console.log("['"+add+"', "+lat+", "+lng+"]");
}
}));
});
return promises;
};
$.when.apply($, addresses()).then(function(){
console.log(arrayLocation);
});
});
There are a couple points of note here:
Returning an array of independent promises means that you cannot feed them to $.when directly because that function is designed to accept multiple individual promises instead of an array; you need to use apply to compensate.
I have moved the declaration of arrayLocation inside the click event handler so that it gets reset each time you click the button. The problem of results being added again after each click was due to this array not being reset.
The final handler does not accept any arguments. That's because the arguments that will be passed are the jqXHR objects representing the individual AJAX requests, which is not really useful. Instead of this it captures arrayLocation by closure, since you know independently that the results will be stored there.
Slightly different approach without a shared global variables
$('button').click(function () {
var addresses = function () {
var arrayOfLines = $('#gps').val().split('\n'),
arrayLocation = [];
$.each(arrayOfLines, function (index, item) {
var town = item.split(',');
var xhr = $.ajax({
url: 'http://maps.googleapis.com/maps/api/geocode/json?address=' + $.trim(town[0]) + '&sensor=false',
dataType: 'json'
});
arrayLocation.push(xhr);
});
return $.when.apply($, arrayLocation).then(function () {
return $.map(arguments, function (args) {
if (!$.isArray(args[0].results) || args[0].results.length == 0) {
return undefined;
}
var data = args[0].results[0];
var location = data.geometry.location;
var add = data.address_components[0].long_name;
var lat = location.lat;
var lng = lng;
return "['" + add + "', " + lat + ", " + lng + "]";
});
});
return arrayLocation;
};
addresses().done(function (arrayLocation) {
console.log(arrayLocation)
})
});
Demo: Fiddle
Related
I have an function inside it i am using $.each method. I want to call another function alertMsg() after $.each completely executed. But when i use breakpoints i can see that before finishing the $.each method it executes the alertMsg function. why? how to solve it.
function test(hospitalJson,blockJson){
$.each(hospitalJson.organisationUnits, function (i, curr_hos) {
if (curr_hos.id == orgUnit.id) {
var stringPath=[];
stringPath= curr_hos.path.split("/");
outerloop:
for(var i=0;i<stringPath.length;i++){
for(var j=0;j<blockJson.length;j++){
if(stringPath[i]==blockJson[j].id){
document.getElementById('blockID').innerHTML = blockJson[j].id;
break outerloop;
}
}
}
// to get district name
var districtNameURL="../api/organisationUnits.json?fields=name&filter=id:in:[" + curr_hos.path.split("/")[4] + "]" ;
$.get(districtNameURL,function(district){
districtName=district.organisationUnits[0].name;
console.log(districtName);
document.getElementById('districtID').innerHTML = districtName;
});
alertMsg = 1;
return false;
}
});
//this message execute before finishing $.each
alert(alertMsg);
}
Due to the fact that $.each has multiple AJAX calls inside, you need to create an array containing Promise objects that need to be resolved . Since you may not know the exact size of the parsed JSON object and jQuery $.when cannot handle arrays you need to extend it's functionality.
function test(hospitalJson, blockJson) {
var deferreds = [];
$.each(hospitalJson.organisationUnits, function(i, curr_hos) {
//...
deferreds.push(
$.get(districtNameURL, function(district) {
districtName = district.organisationUnits[0].name;
console.log(districtName);
document.getElementById('districtID').innerHTML = districtName;
}));
}
return deferreds;
});
}
var resolveData = test(hospitalJson, blockJson);
$.when.apply(null, resolveData).done(function() {
alert(alertMsg);
});
JSfiddle demo
Change:
$.get(districtNameURL,function(district){
districtName=district.organisationUnits[0].name;
console.log(districtName);
document.getElementById('districtID').innerHTML = districtName;
});
To:
$.ajax({
url: districtNameURL,
type: "GET",
async: false
})
.success(function (district) {
districtName = district.organisationUnits[0].name;
console.log(districtName);
document.getElementById('districtID').innerHTML = districtName;
});
This will stop the get action being asynchronous and therefore your logic will be processed in the expected order.
I was wondering if there is a way to pull and use JSON data from two different sources. Currently, the code looks like this:
//JSON1
$.getJSON('url1',function(data){
$.each(data,function(key,val){
//code
});
});
//JSON2
$.getJSON('url2',function(data){
$.each(data,function(key,val){
//code
});
});
When I do this, i seems that variables created from one JSON function aren't available in the other one, which makes it hard for them to be useful together.
Is there a better way to have these two work together?
This function takes an array of urls and a callback as parameters:
function getMultiJSON(urlList,callback) {
var respList = {};
var doneCount = 0;
for(var x = 0; x < urlList.length; x++) {
(function(url){
$.getJSON(url,function(data){
respList[url] = data;
doneCount++;
if(doneCount === urlList.length) {
callback(respList);
}
});
})(urlList[x]);
}
}
You would use it like this:
getMultiJSON(['url1','url2'],function(response) {
// in this case response would have 2 properties,
//
// response.url1 data for url1
// response.url2 data for url2
// continue logic here
});
You might want to add a timeout as the function will never call your handler should any of the URLs fail to load
Variable declared within the functions using var (or blocks, using let) are not available outside of the functions (or blocks).
$.getJSON('url1',function(data){
$.each(data,function(key,val){
var only_accessible_here = key;
});
});
So if you want variables that are accessible outside the scope of the function they are declared in, you need to declare them outside of the function they are used in.
var combined_stuff = ''
$.getJSON('url1',function(data){
$.each(data,function(key,val){
combined_stuff += val;
});
});
//JSON2
$.getJSON('url2',function(data){
$.each(data,function(key,val){
combined_stuff += val;
});
});
As Marc B says, there is no way to know which order the combined_stuff variable will be updated, either by JSON1 first, or by JSON2 first, or by only one, if one of the getJSON calls fail, or by neither if both fail.
If the order of updating is important, call the one you want to use second in the function of the one you want to call first.
var combined_stuff = ''
$.getJSON('url1',function(data){
$.each(data,function(key,val){
combined_stuff += val;
//JSON2
$.getJSON('url2',function(data){
$.each(data,function(key,val){
combined_stuff += val;
});
});
});
});
Easily using the open source project jinqJs (http://www.jinqJs.com)
var data1 = jinqJs().from('http://....').select();
var data2 = jinqJs().from('http://....').select();
var result = jinqJs().from(data1, data2).select();
The example does a sync call, you can do an async call by doing something like this:
var data1 = null;
jinqJs().from('http://....', function(self){ data1 = self.select(); });
Result will contain both results combined.
If you control the endpoint, you could make it return all of the data you want in one shot. Then your data would look like:
{
"url1_data": url1_json_data,
"url2_data": url2_json_data
}
If you still have 2 endpoints you need to hit, you can pass the result of your first ajax call to the second function (but this makes your 2 ajax calls synchronous):
function getJson1(){
$.getJSON('url1',function(data){
getJson2(data);
});
}
function getJson2(json1Data){
$.getJSON('url2',function(data){
//Do stuff with json1 and json2 data
});
}
getJson1();
I would recommend you to use $.when function available in jquery to execute both the methods in parallel and then take the action. See the code snipped below,
var json1 = [], json2 = [];
$.when(GetJson1(), GetJson2()).always(function () {
//this code will execute only after getjson1 and getjson2 methods are run executed
if (json1.length > 0)
{
$.each(json1,function(key,val){
//code
});
}
if (json2.length > 0)
{
$.each(json2,function(key,val){
//code
});
}
});
function GetJson1()
{
return $.ajax({
url: 'url1',
type: 'GET',
dataType: 'json',
success: function (data, textStatus, xhr) {
if (data != null) {
json1 = data;
}
},
error: function (xhr, textStatus, errorThrown) {
json1 = [];//just initialize to avoid js error
}
}
function GetJson2()
{
return $.ajax({
url: 'url2',
type: 'GET',
dataType: 'json',
success: function (data, textStatus, xhr) {
if (data != null) {
json2 = data;
}
},
error: function (xhr, textStatus, errorThrown) {
json2 = [];//just initialize to avoid js error
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
The returned data from each AJAX call are not available outside its own callback function. I'm sure there are more elegant (complex?) solutions, but a couple of simple, Occamic, solutions include global variables, or storing the received data in hidden input elements.
Within each callback function, just loop until the data from the other call is present:
function getJson1(){
$.getJSON('url1',function(data){
var d2 = '';
$('#hidden1').val(data);
while ( d2 == '' ){
//you should use a time delay here
d2 = $('#hidden2').val();
}
getJson2();
});
}
function getJson2(){
$.getJSON('url2',function(d2){
var d1 = '';
$('#hidden2').val(d2);
while ( d1 == '' ){
//you should use a time delay here
d1 = $('#hidden1').val();
}
//Do stuff with json1 and json2 data
});
}
getJson1();
I'm attempting to implement an asynchronous computed observable as show here.
I can do it successfully for one ajax call. The challenge I have at the moment is how to perform various ajax calls in a loop building an array asynchronously and then returning the array to my computed observable array using jQuery promises.
Basically the HTML form works in the following way:
This a student course form.
For each row, users type the person number and on another column they'll type a list of course ids separated by commas. Eg 100, 200, 300.
The purpose of the computed observable is to store an array
containing course details for the courses entered in step 2.
The details are obtained by firing ajax calls for each course and storing HTTP response in the array.
I don't want users to wait for the result, thus the reason to implement an async computed observable.
My problem: I'm having problem returning the value of the final array to the observable. It's always undefined. The ajax calls work fine but perhaps I'm still not handling the promises correctly.
Here's the code for my class:
function asyncComputed(evaluator, owner) {
var result = ko.observable(), currentDeferred;
result.inProgress = ko.observable(false); // Track whether we're waiting for a result
ko.computed(function () {
// Abort any in-flight evaluation to ensure we only notify with the latest value
if (currentDeferred) { currentDeferred.reject(); }
var evaluatorResult = evaluator.call(owner);
// Cope with both asynchronous and synchronous values
if (evaluatorResult && (typeof evaluatorResult.done == "function")) { // Async
result.inProgress(true);
currentDeferred = $.Deferred().done(function (data) {
result.inProgress(false);
result(data);
});
evaluatorResult.done(currentDeferred.resolve);
} else // Sync
result(evaluatorResult);
});
return result;
}
function personDetails(id, personNumber, courseIds) {
var self = this;
self.id = ko.observable(id);
self.personNumber = ko.observable(personNumber);
self.courseIds = ko.observable(courseIds);
// Computed property to extract PIC details for additional PICs.
// This is computed observable which returns response asynchronously
self.courseDetails = asyncComputed(function () {
var courseIdsArray = self.courseIds().split(",");
var arr = [];
var arr_promises = [];
function getCourseDetails(courseId) {
var dfrd = $.Deferred();
var content = {};
content.searchString = courseId;
var url = 'MyURL';
return $.ajax(url, {
type: 'POST',
dataType: 'json',
data: requestData, // content of requestData is irrelevant. The ajax call works fine.
processdata: true,
cache: false,
async: true,
contentType: "application/json"
}).done(function (data) {
arr.push(new PicDetails(data.GenericIdentifierSearchResult[0]));
}).fail(function () {
alert("Could not retrieve PIC details");
}).then(function () {
dfrd.resolve();
});
}
if (courseIdsArray.length > 0) {
$.each(courseIdsArray, function (index, courseId) {
if (courseId.length > 0) {
arr_promises.push(getCourseDetails(courseId));
}
});
};
$.when.apply($, arr_promises).done(function () {
return arr;
})
}, this);
}
I think you dont really need a separate api/code for this.
You could just create observables for every input/value that changes on your site, and create a computed observable based on those.
e.g in rough pseudo code
self.id = ko.observable(id);
self.personNumber = ko.observable(personNumber);
self.courseIds = ko.observable(courseIds);
self.courseDetailsArray = ko.observableArray([]);
self.courseDetails = ko.computed(function() {
//computed the course details based on other observables
//whenever user types in more course ids, start loading them
$.get( yoururl, {self.courseIds and self.id}).success(data) {
when finished async loading, parse the data and push the new course details into final array
self.courseDetailsArray.push( your loaded and parsed data );
//since courseDetailsArray is observableArray, you can have further computed observables using and re-formatting it.
}
});
I have something a bit different from your approach, but you can build something like an asyncComputed out of it if you prefer:
make a simple observable that will hold the result
make a dictionary of promises that you'll basically keep in sync with the array of course ids
when the array of course ids change, add / remove from the dictionary of promises
wrap all your promises in a when (like you're doing) and set the result when they're all done
Basic idea:
var results = ko.observable([]);
var loadingPromises = {};
var watcher = ko.computed(function () {
var ids = ko.unwrap(listOfIds);
if (ids && ids.length) {
ids.forEach(function (id) {
if (!loadingPromises.hasOwnProperty(id)) {
loadingPromises[id] = $.get(url, {...id...});
}
});
var stillApplicablePromises = {};
var promises = []; // we could delete from loadingPromises but v8 optimizes delete poorly
Object.getOwnPropertyNames(loadingPromises).forEach(function (id) {
if (ids.indexOf(id) >= 0) {
stillApplicablePromises[id] = loadingPromises[id];
promises.push(loadingPromises[id]);
}
});
loadingPromises = stillApplicablePromises;
$.when.apply(this, promises).then(function () {
// process arguments here however you like, they're the responses to your promises
results(arguments);
});
} else {
loadingPromises = {};
results([]);
}
}, this);
This is the file (that may change) where you can see this "in real life": https://github.com/wikimedia/analytics-dashiki/blob/master/src/components/wikimetrics-visualizer/wikimetrics-visualizer.js
And the basic fiddle: http://jsfiddle.net/xtsekb20/1/
I'm working on creating a Users collection with the ability to then grab single users inside. This will be used to match from another system, so my desire is to load the users once, and then be able to fine/match later. However, I'm having a problem accessing the outer users collection from an inner method.
function Users(){
var allUsers;
this.getUsers = function () {
// ajax to that Jasmine behaves
$.ajax({
url: '../app/data/jira_users.json',
async: false,
dataType: 'json',
success: function(data) {
allUsers = data;
}
});
return allUsers;
};
this.SingleUser = function (name) {
var rate = 0.0;
var position;
this.getRate = function () {
if(position === undefined){
console.log('>>info: getting user position to then find rate');
this.getPosition();
}
$.ajax({
url: '../app/data/rates.json',
async: false,
dataType: 'json',
success: function(data) {
rate = data[position];
}
});
return rate;
};
this.getPosition = function () {
console.log(allUsers);
//position = allUsers[name];
return position;
};
//set name prop for use later I guess.
this.name = name;
};
}
and the test that's starting all of this:
it("get single user's position", function(){
var users = new Users();
var someone = new users.SingleUser('bgrimes');
var position = someone.getPosition();
expect(position).not.toBeUndefined();
expect(position).toEqual('mgr');
});
The getPosition method is the issue (which might be obvious) as allUsers is always undefined. What I have here is yet another attempt, I've tried a few ways. I think the problem is how the Users.getUsers is being called to start with, but I'm also unsure if I'm using the outer and inner vars is correct.
Though the others are correct in that this won't work as you have it typed out, I see the use case is a jasmine test case. So, there is a way to make your test succeed. And by doing something like the following you remove the need to actually be running any kind of server to do your test.
var dataThatYouWouldExpectFromServer = {
bgrimes: {
username: 'bgrimes',
show: 'chuck',
position: 'mgr'
}
};
it("get single user's position", function(){
var users = new Users();
spyOn($, 'ajax').andCallFake(function (ajaxOptions) {
ajaxOptions.success(dataThatYouWouldExpectFromServer);
});
users.getUsers();
var someone = new users.SingleUser('bgrimes');
var position = someone.getPosition();
expect(position).not.toBeUndefined();
expect(position).toEqual('mgr');
});
This will make the ajax call return whatever it is that you want it to return, which also allows you to mock out tests for failures, unexpected data, etc. You can set 'dataThatYouWouldExpectFromServer' to anything you want at any time.. which can help with cases where you want to test out a few different results but don't want a JSON file for each result.
Sorta-edit - this would fix the test case, but probably not the code. My recommendation is that any time you rely on an ajax call return, make sure the method you are calling has a 'callback' argument. For example:
var users = new Users();
users.getUsers(function () {
//continue doing stuff
});
You can nest them, or you can (preferably) create the callbacks and then use them as arguments for eachother.
var users = new Users(), currentUser;
var showUserRate = function () {
//show his rate
//this won't require a callback because we know it's loaded.
var rate = currentUser.getRate();
}
var usersLoaded = function () {
//going to load up the user 'bgrimes'
currentUser = new users.SingleUser('bgrimes');
currentUser.getRate(showUserRate);
}
users.getUsers(usersLoaded);
your approach to fill the data in allUsers is flawed
the ajax call in jquery is async so every call to users.getAllUsers would be returned with nothing and when later the success function of the jquery ajax is called then allUsers would get filled
this.getUsers() won't work. Its returning of allUsers is independent from the ajax request that fetches the data, because, well, the ajax is asynchronous. Same with getRate().
You'll have to use a callback approach, where you call getUsers() with a callback reference, and when the ajax request completes, it passes the data to the callback function.
Something like:
this.getUsers = function (callback) {
// ajax to that Jasmine behaves
$.ajax({
url: '../app/data/jira_users.json',
async: false,
dataType: 'json',
success: function(data) {
callback(data);
}
});
};
And the call would be along the lines of:
var user_data = null;
Users.getUsers(function(data) {
user_data = data;
});
I have a the following java script object
function eventTypeObj() {
allEventTypes = [];
// When the object is created go and get all the event types that can be included in journey or clusters.
$.ajax({
url: "/ATOMWebService.svc/GetDisplayEventTypes",
dataType: "json",
success: function(result) {
allEventTypes = eval("(" + result.d + ")");
}
});
// Returns a list of all the event type IDS.
this.getEventTypeIds = function() {
var eventTypeIDs = [];
for (var i = 0; i < allEventTypes.length; i++) {
eventTypeIDs.push(allEventTypes[i].Id);
}
return eventTypeIDs;
};
}
I was wondering if there is a way stop some one calling the eventTypeObj.getEventTypeIds(); before the ajax call in the constructor has succeeded, and there is no data in the allEventTypes array?
Something like this would be way better (im not guaranteeing this is 100% working, but the concept is sound):
function eventTypeObj() {
this.allEventTypes = [];
this.hasLoadedEventTypes = false;
var loadEventTypes = function(cb) {
$.ajax({
url: "/ATOMWebService.svc/GetDisplayEventTypes",
dataType: "json",
success: function(result) {
this.allEventTypes = eval("(" + result.d + ")");
this.hasLoadedEventTypes = true;
cb();
}
});
};
this.getEventTypeIds = function(updateEventTypes, callback) {
var _getEventTypeIds = function() {
var eventTypeIDs = [];
for (var i = 0; i < this.allEventTypes.length; i++) {
eventTypeIDs.push(this.allEventTypes[i].Id);
}
return eventTypeIDs;
};
if (!this.hasLoadedEventTypes || updateEventTypes) {
loadEventTypes(function(){ callback(_getEventTypeIds()); });
}
else callback(_getEventTypeIds());
};
}
Example usage:
var eto = new eventTypeObj();
eto.getEventTypeIds(false, function(eventTypeIdArray) {
// do stuff with the id array
});
/*
somewhere later on you want to get an updated eventTypeId array
in case the event types have changed.
*/
eto.getEventTypeIds(true, function(eventTypeIdArray) {
// do stuff with the updated ids
});
var allowCall = false;
function eventTypeObj() {
allEventTypes = [];
// When the object is created go and get all the event types that can be included in journey or clusters.
$.ajax({
url: "/ATOMWebService.svc/GetDisplayEventTypes",
dataType: "json",
success: function(result) {
allEventTypes = eval("(" + result.d + ")");
allowCall = true;
}
});
// Returns a list of all the event type IDS.
this.getEventTypeIds = function() {
if(!allowCall) return; // or pop up a message
var eventTypeIDs = [];
for (var i = 0; i < allEventTypes.length; i++) {
eventTypeIDs.push(allEventTypes[i].Id);
}
return eventTypeIDs;
};
}
Or just check if allEventTypes is empty or not.
There is no way to prevent someone from calling it too soon. What would you want to have happen if they call it too soon?
It looks like your code now currently returns an empty array if allEventTypes hasn't yet been filled in. You can decide whether the empty array is the right result or if you should throw an exception when it's called too early to make it absolutely clear to the caller that the data is not yet available.
You could provide some helper code for people who need that information, but it might not yet be available. For example, you could allow them to register a callback that would get called from the success handler after the data had been filled in. You could allow them to query whether the data is available yet.
If you don't want the responsibility for the timing to be on the callers, then you cannot offer a synchronous way to get this information. Instead, you would only offer a callback mechanism for getting the data. If the data is ready, the callback would get called immediately. If the data is not ready, the callback would get called when the ajax function completes. In either case, the caller would have to process the data in the callback only and getEventTypeIds would not be a normal call to get the data like it is now, but rather a call to register a callback that would be called with the data when was ready. This would relieve the caller from having to know implementation details of when the data was ready, but would force them to use the asynchronous nature of the callback mechanism.
this.getEventTypeIds = function(callback) {
if (allEventTypes.length > 0) {
// data is ready call the callback with the data now
} else {
// store the callback to be called later from the success handler
}
}
You can check if the eventType array is empty, right?
if(allEventTypes.length == 0)
{
return;
}