Assign asynchronous jsonp result to a variable - javascript

I have an asynchronous jsonp-Call and try to assign the result to the variable "FILL_ME" in the example below.
The onSuccess function is called successful, nevertheless it is not possible to assign it's value to the variable "FILL_ME". Instead the error is thrown: Uncaught TypeError: Cannot read property '0' of undefined, which seems like a scope issue.
Please have a look at the following code.
The console.log("final " +FILL_ME) is triggered before the onSuccess method.
How would I assign the result to a variable?
function foo() {
var FILL_ME = 'OVERWRITE ME';
var $jsonp = (function() {
var that = {};
that.send = function(src, options) {
var callback_name = options.callbackName || 'jsonCallback',
on_success = options.onSuccess || function() {},
on_timeout = options.onTimeout || function() {},
timeout = options.timeout || 10; // sec
var timeout_trigger = window.setTimeout(function() {
window[callback_name] = function() {};
on_timeout();
}, timeout * 1000);
window[callback_name] = function(data) {
window.clearTimeout(timeout_trigger);
on_success(data);
}
var script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.src = src;
document.getElementsByTagName('head')[0].appendChild(script);
}
return that;
})();
$jsonp.send('https://run.plnkr.co/plunks/v8xyYN64V4nqCshgjKms/data-2.json', {
callbackName: 'jsonCallback',
onSuccess: function(json) {
console.log('onSuccess!' +json);
FILL_ME = json["sites"][0]["siteName"];
console.log(FILL_ME);
},
onTimeout: function() {
console.log('onTimeout!');
FILL_ME = '';
console.log(FILL_ME);
},
timeout: 5
});
console.log("final " +FILL_ME)
}
<body onLoad="foo()">
</body>

Cannot read property '0' of undefined
is referring to where you try to access the data returned from your jsonp call, specifically where you try to access property 0 of the array:
json["sites "][0]["siteName"];
That means that json["sites "] is undefined. Likely because of the extra space after sites. It seems to work correctly if you remove that space:
http://plnkr.co/edit/UpGMxruppHeh1U8x0j7B
If you want to use the variable outside of your onSuccess callback, you'll have to write another method you call and pass in FILL_ME like so:
function runAfterSuccess(p_FILL_ME) {
console.log("Here I can do whatever I want with FILL_ME", p_FILL_ME);
}
Then in your onSuccess callback use:
runAfterSuccess(FILL_ME);

Related

Javascript not recognizing self.function when inside onclick event

I have a Javascript file that I added to. It's for a twitter plugin, and I'm adding a filter function.
This is my script (the relevant parts):
;(function ( $, window, document, undefined ) {
var pluginName = "twitterFeed",
defaults = {
username: null,
webservice_url: "/services/Scope/Country_United_States_Internet/TwitterService.svc/DoTwitterSearch",
num_tweets: 3
};
// The actual plugin constructor
function Plugin( element, options ) {
this.element = element;
this.options = $.extend( {}, defaults, options );
this._defaults = defaults;
this._name = pluginName;
this.init();
}
Plugin.prototype = {
init: function() {
//if username is unknown
if(this.options.username == null) {
// do nothing
try{console.log('twitter username not found')}catch(err){};
return false;
}
// Get the tweets to display
this.getTweets();
$(".twitter-search input").on("change", function () {
var filters = this.formatFilters($(this).val());
this.getTweets(filters);
});
},
formatFilters : function(filterString) {
var hashtags = filterString.split(" ");
var hashtagString = "";
for (var i = 0; i < hashtags.length; i++) {
var hashtag = hashtags[i];
hashtag = hashtag.replace(",", "");
if (hashtag[0] !== "#") {
hashtag = "#" + hashtag;
}
hashtagString += " " + hashtag;
}
return hashtagString;
},
getTweets : function(filters){
var self = this;
var query = "from:" + self.options.username;
if (filters) {
query += filters;
}
var post_data = JSON.stringify(
{
"PageSize" : self.options.num_tweets,
"TwitterQuery" : query
}
);
$.ajax({
type: "POST", // Change to POST for development environment
url: this.options.webservice_url,
data: post_data,
contentType: "application/json; charset=utf-8",
dataType: "json",
timeout:2000,
success: function(data) {
// render the tweets
self.renderTweets(data.ContentItems);
},
error: function(error, type){
try{console.log(type);}catch(err){}
}
});
},
I added the $(".twitter-search input") on change event (in init) and I added the formatFilters() function. However, in the onchange function, I get the error "this.formatFilters() is not defined". I tried removed this but still got "formatFilters() is not defined.
Remember that this inside of an event handler means whatever HTML element the event was activated on.
Instead, you need to keep track of the actual Plugin object, not the HTML element.
var self = this;
$(".twitter-search input").on("change", function () {
var filters = self.formatFilters($(this).val());
self.getTweets(filters);
});
The problem you are experiencing is with function scope. When you refer to this in the event handler it points to the callback scope and not the scope of your formatFilters function.
To fix it - In the init function add var self = this; on the first line and then change the call to use self.formatFilters instead of this.formatFilters

Callback from API

I have the following script on http://foobar.com/api?callback=mycallback:
var something = 'aaa';
var callback = mycallback;
(function() {
var output = eval(something);
callback(output);
});
And I want to access this script from my own script, and fetch the output. So I am doing the following:
var module1 = (function() {
var getFromApi = function(output) {
return (function(output) {
var script = document.createElement('script');
script.setAttribute('src', 'http://foobar.com/api?callback=mycallback');
document.getElementsByTagName('head')[0].appendChild(script);
});
};
var fetch = function() {
getFromApi(function(output) {
console.log(output);
});
};
return {
fetch: fetch
};
})();
module1.fetch();
The result should be the output from this script, but it is not, it doesn't even enters the callback. How can I do this properly?
There are some problems in module1:
getFromApi returns a function (let's call it fn)
fn has a parameter named output that shadows the argument of the outer function making the argument passed to getFromApi useless
fetch calls getFromApi and it does nothing else
the callback function must be globally accessible
A possible solution could be:
var module1 = (function() {
var getFromApi = function(output) {
var script = document.createElement('script');
script.setAttribute('src', 'http://foobar.com/api?callback='+output);
document.getElementsByTagName('head')[0].appendChild(script);
};
var fetch = function() {
getFromApi('myfunction');
};
return {
fetch: fetch
};
})();
function myfunction(output) {
console.log(output);
}
module1.fetch();
Possible nasty solution for function as callback (It will not work in IE but can be adapted)
var module1 = (function() {
var getFromApi = function(output) {
var script = document.createElement('script');
script.setAttribute('src', 'http://foobar.com/api?callback=eval(document.currentScript.dataset.fn)');
script.setAttribute('data-fn', '(function(){ return ' + output +'})()');
document.getElementsByTagName('head')[0].appendChild(script);
};
var fetch = function() {
return getFromApi(function(output){
console.log(output);
/*remember to remove script tag*/
document.currentScript.remove();
});
};
return {
fetch: fetch
};
})();
module1.fetch();

Initializing object with asynchonous request

This is my object definition:
function DrilledLayer(sourceLayerName, sourceTableName, targetLayerName, targetFieldName, operators, baseLayer=false) {
this.sourceLayerName = sourceLayerName;
this.sourceTableName = sourceTableName;
this.targetLayerName = targetLayerName;
this.targetFieldName = targetFieldName;
this.operators = operators;
this.baseLayer = baseLayer;
this.targetLayerId;
this.drilledLayerId;
this.selectedCode;
this.redraw = false;
this.getTargetLayerId(); //this function must initialize this.targetLayerId
}
DrilledLayer.prototype.getTargetLayerId = function(){
$.soap({
url: 'https://url/version_4.8/services/MapService',
method: 'getLayersIdByName',
appendMethodToURL: false,
data : {
mapInstanceKey: mapKey,
layerName: this.targetLayerName,
},
error: function(){
alert("error getLayersIdByName");
},
success: function(soapResponse){
layerId = soapResponse.toJSON().Body.getLayersIdByNameResponse.getLayersIdByNameReturn.getLayersIdByNameReturn.text;
this.targetLayerId = layerId;
}
});
}
This is how I create the object:
drillCs = new DrilledLayer("Drilled CS", "Cs_Franco_General.TAB", "RA_General", "Code_RA", "=")
If I look into drillCs object there is no targetLayerId property defined, but I know the soap request were made successfuly. Why?
this in JavaScript is mostly set by how a function is called. this during the success callback won't be the same as this during your call to your getTargetLayerId function, you have to remember it.
In this case, the easiest way is probably with a variable:
DrilledLayer.prototype.getTargetLayerId = function(){
var layer = this; // <=== Set it
$.soap({
url: 'https://url/version_4.8/services/MapService',
method: 'getLayersIdByName',
appendMethodToURL: false,
data : {
mapInstanceKey: mapKey,
layerName: this.targetLayerName,
},
error: function(){
alert("error getLayersIdByName");
},
success: function(soapResponse){
layerId = soapResponse.toJSON().Body.getLayersIdByNameResponse.getLayersIdByNameReturn.getLayersIdByNameReturn.text;
layer.targetLayerId = layerId; // <=== Use it
}
});
}
More (on my blog):
You must remember this
Separately, of course, you won't see the properly until the async callback fires (which will be some time after the new call returns), but you seem to be comfortable with the async aspect of this.

Javascript variable scope hindering me

I cant seem to get this for the life of me. I cant access the variable "json" after I calll the getJson2 function. I get my json dynamically through a php script, and that works. But then its gone. there is a sample that I use as a guide at The InfoVis examples where the json is embedded in the init function. i am trying to get it there dynamically.
<script language="javascript" type="text/javascript">
var labelType, useGradients, nativeTextSupport,animate,json;
function getJson2()
{
var cd = getParameterByName("code");
$.get("tasks.php?code="+cd, function(data){
return data;
})
};
function getParameterByName(name)
{
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regexS = "[\\?&]" + name + "=([^&#]*)";
var regex = new RegExp(regexS);
var results = regex.exec(window.location.search);
if(results == null)
return "";
else
return decodeURIComponent(results[1].replace(/\+/g, " "));
}
(function() {
var ua = navigator.userAgent,
iStuff = ua.match(/iPhone/i) || ua.match(/iPad/i),
typeOfCanvas = typeof HTMLCanvasElement,
nativeCanvasSupport = (typeOfCanvas == 'object' || typeOfCanvas == 'function'),
textSupport = nativeCanvasSupport
&& (typeof document.createElement('canvas').getContext('2d').fillText == 'function');
//I'm setting this based on the fact that ExCanvas provides text support for IE
//and that as of today iPhone/iPad current text support is lame
labelType = (!nativeCanvasSupport || (textSupport && !iStuff))? 'Native' : 'HTML';
nativeTextSupport = labelType == 'Native';
useGradients = nativeCanvasSupport;
animate = !(iStuff || !nativeCanvasSupport);
})();
debugger;
var Log = {
elem: false,
write: function(text){
if (!this.elem)
this.elem = document.getElementById('log');
this.elem.innerHTML = text;
debugger;
this.elem.style.left = (500 - this.elem.offsetWidth / 2) + 'px';
}
};
function init(){
json = getJson2();
//init data
var st = new $jit.ST({
//id of viz container element
injectInto: 'infovis',
//set duration for the animation
duration: 800,
//set animation transition type ..................
function getJson2()
{
var cd = getParameterByName("code");
$.get("tasks.php?code="+cd, function(data){
return data;
})
};
getJson2() doesn't return anything. The callback function to $.get() returns something, but nothing is listening for that return.
It sounds like you want synchronous loading instead. $.get() is just shorthand for this $.ajax() call: (See docs)
$.ajax({
url: url,
data: data,
success: success,
dataType: dataType
});
And $.ajax() supports more features, like setting async to false.
$.ajax({
url: "tasks.php?code="+cd,
async: false,
dataType: 'json',
success: function(data) {
// data !
}
});
Which means, getJson2 then becomes:
function getJson2()
{
var cd = getParameterByName("code");
var jsonData;
$.ajax({
url: "tasks.php?code="+cd,
async: false,
dataType: 'json',
success: function(data) {
jsonData = data;
}
});
return jsonData;
};
var myJsonData = getJson2();
Or still use $.get async style, and use callbacks instead.
function getJson2(callback)
{
var cd = getParameterByName("code");
$.get("tasks.php?code="+cd, function(data){
callback(data);
});
};
getJson2(function(data) {
// do stuff now that json data is loaded and ready
});
The $.get call is asynchronous. By the time you call return data;, the function has already long since returned. Create a variable outside of your function's scope, then in the $.get callback handler, assign data to that variable.
var json;
function getJson2(){
// ...
$.get(...., function(response){
json = response;
}
});
Alternatively, you could do a sychronous Ajax call, in which case returning your data would work (but of course would block script execution until the response was recieved). To accomplish this, see the asynch argument to jQuerys $.ajax function.
A jQuery $.get call is asynchronous and actually returns a promise, not the data itself.
An elegant way to deal with this is to use the then() method:
$.get(...).then(function(data){...});
Alternately, change your ajax settings to make the call synchronous.
$.get("tasks.php?code="+cd, function(data){
return data;
})
$.get is asynchroneous. So your value is never returned. You would have to create a callback.
The same question appears here:
Return $.get data in a function using jQuery

How to use YAHOO.util.Connect.asyncRequest and return results?

I'm using YAHOO.util.Connect.asyncRequest to get data from database, here is the code :
function getCountArticle(contentCurValue) {
var handleSuccess = function (res) {
var countPubmed = YAHOO.lang.JSON.parse(res.responseText);
var contentCountPubmed = countPubmed.totalArticleRecords;
alert(contentCountPubmed); //return 15 for example
};
var handleFailure = function () {
alert("Error connecting data : Bad pubmed query");
};
var callback =
{
success:handleSuccess,
failure:handleFailure,
timeout: 5000
};
var sURL = 'qct-list-article.html?term=' + contentCurValue + '&retstart=0' + '&retmax=1';
var request = YAHOO.util.Connect.asyncRequest('GET',sURL,callback);
}
I would like this function return : "contentCurValue" (eg:15), but when I try to use this code I get "undefined" :
var test = getCountArticle();
alert(test); // return undefined, should return 15
My error is probably due to asynchronous query, but how can I force "var test = getCountArticle();" to wait for results ?
Since the call is by nature asynchronous, rather than try to wait for the response, you would be better off specifying a callback function to execute with the data. You could modify your method like this:
function getCountArticle(contentCurValue, callback) {
var handleSuccess = function (res) {
var countPubmed = YAHOO.lang.JSON.parse(res.responseText);
var contentCountPubmed = countPubmed.totalArticleRecords;
callback(contentCountPubmed); //return 15 for example
};
// ...
}
then your calling code would be:
getCountArticle("contentCurValue", function(test) {
alert(test);
});
Any further execution using the value returned from your AJAX query would proceed inside of your callback method.
This SO post is essentially the same problem, but not YUI specific: Getting undefined in javascript when calling ajax

Categories

Resources