I'm discovering the concept of "objects" in JavaScript. I'm making an RSS Parser, and I have an error (commented).
function MyParser (feed_url) { // Construct
"use strict";
this.feedUrl = feed_url;
this.pubArray = [];
if (typeof (this.init_ok) == 'undefined') {
MyParser.prototype.parse = function () {
"use strict";
var thisObj = this;
$.get(this.feedUrl, function (data, textStatus, jqXHR) {
if (textStatus == 'success') {
var xml = jqXHR.responseXML,
//lastBuildDate = new Date($(xml).find('lastBuildDate').text());
items = $(xml).find('item');
items.each(function () {
var pubSingle = thisObj.makeObj($(this).find('pubDate').text(),
$(this).find('link').text(),
$(this).find('title').text(),
$(this).find('description').text(),
$(this).find('encoded').text(),
$(this).find('commentRss').text(),
$(this).find('comments').last().text());
thisObj.pubArray.push(pubSingle);
});
console.log(thisObj.pubArray); // OK
}
}, 'xml');
console.log(this.pubArray); // Empty
return (this.pubArray);
};
MyParser.prototype.makeObj = function (pubDate, pubLink, pubTitle, pubDesc, pubContent, pubComCount, pubComLink) {
"use strict";
var pubSingle = {};
pubSingle.pubDate = new Date(pubDate);
pubSingle.pubLink = pubLink;
pubSingle.pubTitle = pubTitle;
pubSingle.pubDesc = pubDesc;
pubSingle.pubContent = pubContent;
pubSingle.pubComCount = pubComCount;
pubSingle.pubComLink = pubComLink;
return (pubSingle);
};
}
this.init_ok = true;
}
If you look at the console.log(), you'll see that the line // OK is outputting my array correctly.
But later, when returning from $.get, my array is empty.
Does anybody have an idea why, and how to correct that please?
This is not a problem with variable-scope. The problem here is that you're working with asynchronous flow and you're not thinking correctly the flow.
Let me explain:
When you do your .get, you fire a parallel asynchronous process that will request information from the browser, but your main program's flow keeps going, so when you get to your "return" statement, your array has not been filled yet with the response from your get method.
You should use your array from inside the get callback and not outside of it, since you can't guarantee that the array will have the information you need.
Does it make any sense?
Let me know!
Further explanation
According to your comments, you're still doing something like this:
var results = MyParser(feed_url);
//code that uses results.pubArray
And you cannot do that. Even though you're setting your "pubArray" inside your .get callback, you're trying to use pubArray right after you called MyParser and that's before the .get callback is called.
What you have to do, is call your next step on your program's logic from within the .get callback... that's the only way you can be sure that the pubArray is filled with proper data.
I hope that makes it clearer.
This is because your line
console.log(this.pubArray); // Empty
is being called directly after you issue your Ajax request; it hasn't had time to fetch the data yet. The line
console.log(thisObj.pubArray); // OK
is being called inside the Ajax callback, by which time the data has been fetched.
Thank you all, and particulary #Deleteman .
Here is what I did:
$.get(this.feedUrl, 'xml').success(function () {
thisObj.handleAjax(arguments[0], arguments[1], arguments[2]);
$(document).trigger('MyParserDone');
}).error(function () {
$(document).trigger('MyParserFailed');
});
Then, when i enter "HandleAjax", i'm back in my object context, so "this" refers to my object and the right properties. The only "problem" is that I have to set a listener (MyParserDone) to make sure the parsing is finished.
Related
Please bare bear with me, I'm very new to Javascript. I am pulling my hair out trying to figure out why this won't work. Keep in mind I come from a Java background. I have a function 'getCsvData' and I'm essentially trying to parse a CSV file and dynamically add object properties to the datasource object and then return it. As you can see, outside the function 'getCsvData', I try to log the results after calling my function, but the result object is empty and there are no object propeties added to it.
I have a very strong feeling it has to due with closure/scope chain resolution that I'm still trying to learn and understand.
The questions are: Why aren't the properties added dynamically to the datasource object? I believe they actually are added in the scope of the anonymous function 'function(data)' passed as the second argument to '$.get', but they are immediately gone once the outer function 'getCsvData' returns. Why, and how can I fix this? Thanks!!
<script src="js/jquery-1.10.2.min.js"></script>
<script src="js/knockout-3.0.0.js"></script>
<script src="js/globalize.min.js"></script>
<script src="js/dx.chartjs.js"></script>
<script src="js/jquery.parse.js"></script>
$(function () {
function getCsvData(fileName, groupBy, year) {
var datasource = { }
$.get(fileName, function(data) {
var alldata = $.parse(data, { header: true });
for (var i = 0; i<alldata.results.rows.length;i++) {
var key = alldata.results.rows[i][groupBy]
if (key in datasource) {
datasource[key] = datasource[key] + 1
} else {
datasource[key] = 0
}
}
});
return datasource;
};
var results = getCsvData("data/data.csv", "Priority", 2012);
console.log(results)
for (key in results) {
console.log(key)
}
});
This is because get is called async, so datasource is the return value after initiating the get rather than after receiving the result (i.e. it is empty because the get completion has not been called yet). You should rather indicate completion with a callback or use jQuery.ajax() with the sync option to wait for the response to the get before returning from getCsvData. See here.
I am having some issues with scope in JS, which I am just picking up.
I've defined an object and am making a .getJSON() call within it, but I don't seem to be able to correctly refer to the calling object's attributes:
// Vehicle object
function vehicle(id) {
this.id = id;
var that = this;
// Fetch some JSON
$.getJSON("json.php?act=vehicleInfo&id=" + this.id, function (json) {
that.vehicleInfo = json
that.icon = L.AwesomeMarkers.icon({ icon: that.vehicleInfo.icon, color: that.vehicleInfo.colour });
that.polyline = new L.Polyline([[that.vehicleInfo.latitude, that.vehicleInfo.longitude]]);
that.marker = L.marker([that.vehicleInfo.latitude, that.vehicleInfo.longitude], {icon: that.icon});
that.marker.bindPopup("Test point");
that.marker.addTo(map);
that.polyline.addTo(map);
});
}
// Vehicle move method
vehicle.prototype.move = function(latlng){
this.marker.setLatLng(latlng);
this.polyline.addLatLng(latlng);
}
When I call .move(), this.marker is undefined. Where am I going wrong here?
Unfortunately, Ajax doesn't work that way. You can't depend on the $.getJSON callback completing at any particular time or even at all. One possibility is to make the request synchronous, but this is not recommended because it locks up the browser.
The only two solutions that are possible are:
Do not depend on ajax
Make anything that depends on the result of an ajax callback depend on the callback itself.
That is any code that calls .move for a vehicle has to be done as a result of the $.getJSON call. You can make it look a bit more elegant, though:
this.jqxhr = $.getJSON(...
/* snip */
vehicle.prototype.move = function (latlng) {
var veh = this;
this.jqxhr.done(function () {
veh.marker.setLatLng(latlng);
veh.polyline.setLatLng(latlng);
});
}
I'm building a plugin that fetches info for a bunch of images in JSON, then displays them in some dialog for selection. Unfortunately, my first intuition pretty clearly results in a race condition:
var ImageDialog = function () {};
ImageDialog.prototype.items = [];
ImageDialog.prototype.fetch_images() {
var parse_images = function(data) {
// Magically parse these suckers.
data = awesome_function(data);
this.items = data;
};
magicalxhrclass.xhr.send({"url": 'someurl', "success": parse_images, "success_scope": this});
}
ImageDialog.prototype.render = function () {
this.fetch_images();
// XHR may or may not have finished yet...
this.display_images();
this.do_other_stuff();
};
var monkey = new ImageDialog();
monkey.render();
Off of the top of my head, I think I could fix this by changing the parse_images callback to include the rest of the render steps. However, that doesn't look quite right. Why would the fetch_images method be calling a bunch of things about displaying images?
So: what should I do here?
I am pretty certain deferreds would help, but alas: I need to write this without any external libraries. :(
Comments on other code smells would be nice, too!
In general, the basic idea you can use is that when a regular program would use a return statement (meaning, "My function is done now do your job!") an asynchronous continuation-passing program would instead use a ballcabk function that gets explicitly called
function fetch_images(callback){
magicalXHR({
success: function(data){
parse_images(data);
callback(whatever);
}
}
}
or, if parse_images is itself an async function:
parse_images(data, callback)
Now when you call fetch_images the code after it goes into a callback instead of assuming that fetch_images will be done when it returns
fetch_images(function(
display_images()
})
By using callbacks you can emulate pretty well what a traditional program could do (in fact its a fairly mechanical translation between one form of the other). The only problem you will now encounter is that error handling gets tricky, language features like loops don't play well with async callbacks and calbacks tend to nest into callback hell. If the callbacks start getting too complex, I would investigate using one of those Javascript dialects that compiles down to continuation-passing-style Javascrit (some of them work without needing extra libraries at runtime).
How about this?
var ImageDialog = function () {
this.items = []; // just in case you need it before the images are fetched
};
ImageDialog.prototype.fetch_images(callback) {
var that = this;
function parse_images (data) {
// Magically parse these suckers.
data = awesome_function(data);
that.items = data;
callback.apply(that);
};
magicalxhrclass.xhr.send({"url": 'someurl', "success": parse_images, "success_scope": this});
}
ImageDialog.prototype.render = function () {
this.fetch_images(function(){
this.display_images();
this.do_other_stuff();
});
};
var monkey = new ImageDialog();
monkey.render();
Here's a thought about what to do.
ImageDialog.prototype.fetch_images() {
var parse_images = function(data) {
// Magically parse these suckers.
data = awesome_function(data);
this.items = data;
fetch_images.caller() // Unfortunately, this is nonstandard/not in the spec. :(
};
magicalxhrclass.xhr.send({"url": 'someurl', "success": parse_images, "success_scope": this});
}
ImageDialog.prototype.render = function () {
if (this.items === []) {
this.fetch_images()
return;
} else {
this.display_images();
this.do_other_stuff();
};
};
This way I'm not passing some implementation detail to fetch_images, and I get caching, to boot. Am I still trying too hard to escape CPS, or is this sensible?
One of the issues I've been encountering again and again is the reference to the this pointer changing. Take the below example. I want to create a Server object, and store the resolution of the camera as a property. This is not possible, as this.resolution applies to a property in the camera callback object instead of the Server object.
function Server(options) {
this.settings = options.settings;
this.camera = options.camera;
// Grab camera resolution
this.camera.getImageResolution(function(err, data) {
this.resolution = data;
});
}
Server.prototype.start = function() {
console.log(this.resolution); // This outputs an undefined variable error
}
In the past, I have worked around this by renaming this to self temporarily to call functions. This does not work when I am storing a value. I would need to to pass this into the callback, which I obviously cannot do.
Additionally, I cannot use apply, as that would not allow camera.getImageResolution to call its own methods.
What is the best route to take in solving this issue? Please ask for clarification if my question is vague.
function Server(options) {
var self = this;
self.settings = options.settings;
self.camera = options.camera;
// Grab camera resolution
this.camera.getImageResolution(function(err, data) {
self.resolution = data;
});
}
Server.prototype.start = function () {
return this.resolution;
}
var server = new Server({options: {...}, settings: {...}});
server.camera.getImageResolution();
// after getImageResolution's asynch method has completed
server.start() // === data parameter from getImageResolution's asynch method callback
I am currently in the process of making my first Titanium iPhone app.
In a model I got:
(function() {
main.model = {};
main.model.getAlbums = function(_args) {
var loader = Titanium.Network.createHTTPClient();
loader.open("GET", "http://someurl.json");
// Runs the function when the data is ready for us to process
loader.onload = function() {
// Evaluate the JSON
var albums = eval('('+this.responseText+')');
//alert(albums.length);
return albums;
};
// Send the HTTP request
loader.send();
};
})();
and I call this function in a view like:
(function() {
main.ui.createAlbumsWindow = function(_args) {
var albumsWindow = Titanium.UI.createWindow({
title:'Albums',
backgroundColor:'#000'
});
var albums = main.model.getAlbums();
alert(albums);
return albumsWindow;
};
})();
however it seems like the call to the model (which fetches some data using HTTP) doesn't wait for a response. In the view when I do the alert it haven't received the data from the model yet. How do I do this in a best-practice way?
Thanks in advance
OK,
Something like this,
function foo(arg1, callback){
arg1 += 10;
....
... Your web service code
....
callback(arg1); // you can have your response instead of arg1
}
you will call this function like this,
foo (arg1, function(returnedParameter){
alert(returnedParameter); // here you will get your response which was returned in above function using this line .... callback(arg1);
});
so here arg1 is parameter (simple parameter like integer, string etc ... ) and second argument is your call back function.
Cheers.
What you need is Synchronous call to web service, so that it will wait till you get the response from the service.
To achieve this in java script you have to pass callback function as parameter and get the return value in callback function instead of returning value by return statement.
Actually coding style you are using is new for me because i am using different coding style.
But the main thing is you have to use call back function to retrieve value instead of return statement. Try this and if you still face the problem than tell me i will try to give an example.
the callback way like zero explained is nicely explained, but you could also try to get it handled with events.
(function() {
main.ui.createAlbumsWindow = function(_args) {
var albumsWindow = Titanium.UI.createWindow({
title:'Albums',
backgroundColor:'#000'
});
var status = new object(), // eventlistener
got_a_valid_result = false;
// catch result
status.addEventListener('gotResult',function(e){
alert(e.result);
got_a_valid_result = true;
});
// catch error
status.addEventListener('error',function(e){
alert("error occured: "+e.errorcode);
git_a_valid_result = true;
});
var albums = main.model.getAlbums(status);
// wait for result
while (!got_a_valid_result){};
return albumsWindow;
};
})();
and your model may something like
main.model.getAlbums = function(status) {
var loader = Titanium.Network.createHTTPClient();
loader.open("GET", "http://someurl.json");
loader.onload = function() {
var albums = eval('('+this.responseText+')');
status.fireEvent('gotResult',{result:albums});
return albums;
};
loader.onerror = function(e){
status.fireEvent('error',{errorcode:"an error occured"});
};
// Send the HTTP request
loader.send();
};
Just as a suggestion, try to use JSON.parse instead of eval as there are risks involved with using eval since it runs all javascript code.
I think that the solution The Zero posted is likely better for memory management, but I'm not totally sure. If you do and eventListener, be aware of the following
(see https://wiki.appcelerator.org/display/guides/Managing+Memory+and+Finding+Leaks)
function doSomething(_event) {
var foo = bar;
}
// adding this event listener causes a memory leak
// as references remain valid as long as the app is running
Ti.App.addEventListener('bad:idea', doSomething);
// you can plug this leak by removing the event listener, for example when the window is closed
thisWindow.addEventListener('close', function() {
// to remove an event listener, you must use the exact same function signature
// as when the listener was added
Ti.App.removeEventListener('bad:idea', doSomething);
});