Parallel JSONP requests in jQuery do not trigger multiple "callback events"? - javascript

I am experiencing an issue in jQuery when I do multiple jsonp requests, all with the same jsonpCallback function. It seems that only for the one of those the callback function is triggered. Are JSONP requests somehow overwriting each other?
Below an example of doing 2 jsonp request to github, and even though both firebug shows that both of them return, the callback function getName is only called for one of them:
function getName(response){
alert(response.data.name);
}
function userinfo(username){
$.ajax({
url: "https://api.github.com/users/" + username,
jsonpCallback: 'getName',
dataType: "jsonp"
});
}
users = ["torvalds", "twitter", "jquery"]
for(var i = 0; i < users.length; i++){
userinfo(users[i]);
}

Your request fired only once because of how jsonp works.
Jsonp means adding a script tag to the page from an outside domain to get around Cross-Site Scripting protections built into modern browsers (and now IE6 and 7 as of April 2011). In order to have that script interact with the rest of the script on the page, the script being loaded in needs to call a function on the page. That function has to exist in the global namespace, meaning there can only be one function by that name. In other words, without JQuery a single jsonp request would look like this:
<script>
function loadJson(json) {
// Read the json
}
</script>
<script src="//outsidedomain.com/something.js"></script>
Where something.js would look like this:
loadJson({name:'Joe'})
something.js in this case has a hard-coded callback to load the JSON it carries, and the page has a hard-coded loadJson function waiting for scripts like this one to load and call it.
Now suppose you want to be able to load json from multiple sources and tell when each finishes, or even load JSON from the same source multiple times, and be able to tell when each call finishes - even if one call is delayed so long it completes after a later call. This hard-coded approach isn't going to work anymore, for 2 reasons:
Every load of something.js calls the same loadJson() callback - you have no way of knowing which request goes with which reply.
Caching - once you load something.js once, the browser isn't going to ask the server for it again - it's going to just bring it back in from the cache, ruining your plan.
You can resolve both of these by telling the server to wrap the JSON differently each time, and the simple way is to pass that information in a querystring parameter like ?callback=loadJson12345. It's as though your page looked like this:
<script>
function loadJson1(json) {
// Read the json
}
function loadJson2(json) {
// Read the json
}
</script>
<script src="//outsidedomain.com/something.js?callback=loadJson1"></script>
<script src="//outsidedomain.com/somethingelse.js?callback=loadJson2"></script>
With JQuery, this is all abstracted for you to look like a normal call to $.ajax, meaning you're expecting the success function to fire. In order to ensure the right success function fires for each jsonp load, JQuery creates a long random callback function name in the global namespace like JQuery1233432432432432, passes that as the callback parameter in the querystring, then waits for the script to load. If everything works properly the script that loads calls the callback function JQuery requested, which in turn fires the success handler from the $.ajax call.
Note that "works properly" requires that the server-side reads the ?callback querystring parameter and includes that in the response, like ?callback=joe -> joe({.... If it's a static file or the server doesn't play this way, you likely need to treat the file as cacheable - see below.
Caching
If you wanted your json to cache, you can get JQuery to do something closer to my first example by setting cache: true and setting the jsonpCallback property to a string that is hardcoded into the cacheable json file. For example this static json:
loadJoe({name:'Joe'})
Could be loaded and cached in JQuery like so:
$.ajax({
url: '//outsidedomain.com/loadjoe.js',
dataType: 'jsonp',
cache: true,
jsonpCallback: 'loadJoe',
success: function(json) { ... }
});

Use the success callback instead..
function userinfo(username){
$.ajax({
url: "https://api.github.com/users/" + username,
success: getName,
dataType: "jsonp"
});
}

$(function() {
function userinfo(username){
var XHR = $.ajax({
url: "https://api.github.com/users/" + username,
dataType: "jsonp"
}).done(function(data) {
console.log(data.data.name);
});
}
users = ["torvalds", "twitter", "jquery"];
for(var i = 0; i < users.length; i++){
userinfo(users[i]);
}
}); ​

Not sure but the response I get from that call to the github API does not include gravatar_id.
This worked for me:
function getGravatar(response){
var link = response.data.avatar_url;
$('#list').append('<div><img src="' + link + '"></div>');
}

Related

jQuery $.when().done() not working as expected with JSONP when loading a local .json file

I'm building an SPA using Sammy and Knockout powered by a REST Web Service available on a different URL.
I'm noticing some odd behavior when returning JSONP versus JSON when using $.when().done()...
.done() never fires, but .fail() will, even though the status code I receive is 200, and JSONP Linter tells me that my JSONP is valid:
(function($) {
$(function() {
$.when($.getJSON('endpoint1?callback=?', null),
$,getJSON('endpoint2?callback=?', null))
.done(function(resp1, resp2) {
console.log(resp1); // this is never called
});
})
.fail(function(obj) {
console.log(obj); // this is called, but why?
});
});
})(jQuery);
A sample response returned is:
callback({
"external-links": [
{
"nav_link_text": "Stack Overflow",
"url": "http://stackoverflow.com"
}
]
});
If I return JSON instead of JSONP, .done() works as expected. What am I doing wrong or need to change?
The problem is specified here:
ReferenceError: callback is not defined
Your JSONP response has callback hard-coded. That's incorrect. JSONP needs to set the function name dynamically.
When jQuery sends a JSONP request, it creates a function called jQuery123456 (or something like that) and sends that name in the request. It calls endpoint1?callback=jQuery123456. The job of JSONP is to make a call to that function. Your JSONP needs to return:
jQuery123456({
your: 'data'
})
You need to use the value of the callback parameter.
If for some reason, creating the JSONP "dynamically" isn't an option, you can force jQuery to name the callback function it creates. You need to use $.ajax for this:
$.ajax({
url: 'endpoint1',
dataType: 'jsonp',
jsonp: false, // Don't add the "?callback=?" param,
// you're not using it anyway
jsonpCallback: 'callback' // Force jQuery to use "callback"
// as the function name
});
Note: jQuery probably won't like having the same callback value for multiple requests.

Trouble understanding JSONP with jQuery

If I wish to fetch data from a remote server, then JSONP is the tool of choice I believe. But I am confused by an example I have seen:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$.ajax({
dataType: 'jsonp',
data: 'p3=c',
jsonp: 'callback',
url: 'http://someserver.com/app?p1=a&p2=b',
success: function (data) {
console.log("data="+data);
$.each(data, function (i, r) {
console.log("i="+i);
console.log("r="+r);
});
},
});
});
</script>
I can see that in the request, a callback parameter has been added with value in the format jQuery1234567890. When I look at the app that processes that request, it extracts the callback parameter from the request and wraps the json data to be returned with that and relevant brackets, so it ends up returning something like this:
jQuery1234567890([{"x":"100","y":"101"},{"x":"200","y":"201"}])
So my first questions are:
(1) Is the app correct to have done what it has?
(2) What has jQuery / JSONP actually done for us?
I was assuming that jQuery would see the dataType of "jsonp", insert a script tag into the DOM, the browser would then download and execute the script. If that's right, has jQuery created the function jQuery1234567890, the implementation of which is to pass the parameter on to the success function?
(3) Is my understanding correct (I don't think it is)?
Thank you,
Paul
(1) Is the app correct to have done what it has?
Yes, that's a correct JSONP format
(2) What has jQuery / JSONP actually done for us?
Notified the server application that JSONP is desired by placing a &callback=jQuery1234567890 in the request
I was assuming that jQuery would see the dataType of "jsonp", insert a script tag into the DOM, the browser would then download and execute the script. If that's right, has jQuery created the function jQuery1234567890, the implementation of which is to pass the parameter on to the success function?
(3) Is my understanding correct (I don't think it is)?
Yes, your understanding is correct. It has created a script with a jQuery1234567890 function which is invoked when the requested scripted is loaded. And as you stated the parameter receives the data and passes it on to the $.ajax internals, which invokes the success callback
From the ajax docs for the jsonp option:
Override the callback function name in a jsonp
request. This value will be used instead of 'callback' in the
'callback=?' part of the query string in the url. So
{jsonp:'onJSONPLoad'} would result in 'onJSONPLoad=?' passed to the
server.
So using jsonp: 'callback' overrides callback with callback, essentially doing nothing.
The other stuff you're seeing is generated by jQuery so that you don't have to do it yourself. You get to simply treat this request like any other ajax request in jquery and not worry about the implementation of the jsonp.

How do I call a JS callback when a file upload completes?

I'm creating frontend upload for an app with appengine backend.
What I want to make is a file upload solution, and I dont want to be using plupload or those kinds of ready-made solutions.
I basically submitted the images to an iframe and then put a cover while it is uploading. Then after it finishes I performed an ajax call to get the image ids for the next view to be rendered. However, the render is always called before the upload is completed, thus I'm not getting any image ids from the backend. can anyonehelp?
here's my code for the upload
perform_input3:(event)=>
event.preventDefault()
$('#product-input-3').hide()
$('#product-input-3').submit()
$('#upload-cover').show()
item_id = $('#item3_id').val()
app.views.imageselect.render(item_id)
the app.views.imageselect.render(item_id) is below:
render:(data)=>
#item_id = data
item_id = #item_id
$.ajax(
url: '/get_image_list/'
type: 'GET'
dataType: 'json'
data: {item_id: item_id}
success:(data) =>
#payload = data
$(#el).append imageSelectTemplate(#payload)
return #
)
I dont want to be using setTimeout function since it will not be flexible depending on the connection speed. Any help will be appreciated :)
Essentially, your question boils down to this: You want to wait to make your Ajax call to the server until the data you're requesting is available. Getting notifications from the server is tricky (depending on how your backend is implemented), so the best solution to your problem is probably to just make the Ajax call periodically (say, once per second) until you get a successful response from the server.
Here's some code that should do that:
do ajaxCall = =>
$.ajax
url: '/get_image_list/'
type: 'GET'
dataType: 'json'
data: {item_id: item_id}
success: (data) =>
#payload = data
$(#el).append imageSelectTemplate(#payload)
error: ->
setTimeout ajaxCall, 1000
If you are only targeting modern browsers, then XHR2's FormData can enable a very simple and elegant approach.
The concept is:
add file(s) binary data to a FormData object
make a $.ajax() call with the FormData object as the AJAX call's "data" parameter
when upload is done, the $.ajax()'s success() or complete() callbacks will be triggered
This approach works with the latest Firefox, Chrome, Safari - http://caniuse.com/xhr2.
See this post for details: Sending multipart/formdata with jQuery.ajax
What you're missing is some sort of callback from the $('#product-input-3').submit() call. I think the following would work (pardon my bad CoffeeScript):
perform_input3:(event)=>
event.preventDefault()
item_id = $('#item3_id').val()
$('#product-input-3').hide()
$('#upload-cover').show()
$('#product-input-3').submit()
$('#target-iframe').ready ->
app.views.imageselect.render(item_id)
This is predicated on the idea that calling 'submit' immediately puts the target iframe into non-ready state, which seems reasonable, but I'd test it. Once it finishes loading The other option I've seen around is to have the page the iframe loads call back into its parent (top-level) window. In JS, something like:
parent.imageUploaded()
Or, if you want to use bound events:
parent.$(parent.document).trigger('upload-complete')
Where, of course, you've set up an upload-complete event on the top-level document object.

Having problems with jQuery, ajax and jsonp

I am using jsonp and ajax to query a web-service written in java on another server. I am using the following jquery command:
$.ajax({
type: "GET",
url: wsUrl,
data: {},
dataType: "jsonp",
complete: sites_return,
crossDomain: true,
jsonpCallback: "sites_return"
});
function jsonp_callback(data) {
console.log(data);
}
function sites_return(data) {
console.log(data);
}
So my problem is that after the query finishes a function called jsonp_callback is called. Where I can clearly see the json formatted string:
{"listEntries":["ELEM1", "ELEM2", "ELEM3", etc...]}
But after the function sites_return is called when the complete event fires, I get the the following:
Object { readyState=4, status=200, statusText="parsererror"}
Also for reference the jsonp_callback function is called before the sites_return function. Also if i take the jsonp_callback function out of the code, I get a complaint it firebug that the function is not implemented.
My question three fold:
1) What am i doing wrong on the jquery side?
2) Why does the json get parsed correctly in jsonp_callback but not sites_return?
3) What can i do to fix these issues?
EDIT
Some new development. Per the comments here is some additional information.
The following is what comes out of the http response
jsonp_callback({"listEntries":["ELEM1", "ELEM2", "ELEM3"]})
I assume this is the reason jsonp_callback is being called. I guess my question now becomes, is there any way to control this (assuming i don't have access to the back end web-service).
Hope this helps~
var url = "http://maps.google.com/maps/api/geocode/json?address=1600+Amphitheatre+Parkway,+Mountain+View,+CA&sensor=false";
var address = "1600+Amphitheatre+Parkway";
var apiKey = "+Mountain+View,+CA";
$.getJSON("http://maps.google.com/maps/geo?q="+ address+"&key="+apiKey+"&sensor=false&output=json&callback=?",
function(data, textStatus){
console.log(data);
});
I believe that the first argument to the sites_return function would be the jqXHR Object. Instead of complete try using success.
But still this may not work as it seems that there is a parsing error (mentioned in the return value of sites_return function called from oncomplete). Therefore, you would first need to check your json string.
To Validate JSON, you can use http://jsonlint.com/
I think that the problem is that your server is not behaving the way jQuery expects it to. The JSONP "protocol" is not very stable, but generally what's supposed to happen is that the site should look for the "callback" parameter and use that as the function name when it builds the JSONP response. It looks as if your server always uses the function name "jsonp_callback".
It might work to tell jQuery that your callback is "jsonp_callback" directly:
$.ajax({
type: "GET",
url: wsUrl,
data: {},
dataType: "jsonp",
complete: sites_return,
crossDomain: true,
jsonpCallback: "jsonp_callback"
});
Not 100% sure however.
If you don't have the ability to change the JSONP function wrapper that the remote server returns, jQuery's $.ajax() may be overkill here. Ultimately, all you're doing is injecting a script reference to wsUrl, which makes a call to jsonp_callback with a JavaScript object literal as its input parameter.
You could just as easily do something like this and avoid the confusion around the callback naming/syntax:
$.getScript(wsUrl);
function jsonp_callback(response) {
// Access the array here via response.listEntries
}

jQuery + JSONP + Yahoo Query Language

I want to get live currency rates from an external source, so I found this great webservice:
Currency Convertor
This service is working like a charm, the only downside is that it does not provide JSONP results, only XML. Therefore we have a cross browser problem while trying to consume this webservice using jQuery $.ajax().
So I found Yahoo Query Language which returns results as JSONP and also mangae to consume other webservices and return me the results. This is also working, here is an example URL:
http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20xml%20where%20url%3D'http%3A%2F%2Fwww.webservicex.net%2FCurrencyConvertor.asmx%2FConversionRate%3FFromCurrency%3DNOK%26ToCurrency%3DEUR'&format=json&diagnostics=true&callback=cbfunc
This URL return JSONP result and is working like a charm, but the problem appears when I use this in my code:
$.ajax({
type: "GET",
url: urlToWebservice,
contentType: "application/json; charset=utf-8",
dataType: "jsonp",
success: function(data) {
$("#status").html("OK: " + data.text);
},
error: function(xhr, textStatus, errorThrown) {
$("#status").html("Unavailable: " + textStatus);
}
});
When I try to run this code nothing happens, and I can see this error message in my Firebug javascript debugger:
cbfunc is not defined
cbfunc is the name of the container which surrounds the JSON response, but why does it say not defined?
EDIT:
This is my new code, but I still get the cbfunc is not defined
$.ajax({
url: "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20xml%20where%20url%3D'http%3A%2F%2Fwww.webservicex.net%2FCurrencyConvertor.asmx%2FConversionRate%3FFromCurrency%3DNOK%26ToCurrency%3DEUR'&format=json&callback=cbfunc",
dataType: 'jsonp',
jsonp: 'callback',
jsonpCallback: 'cbfunc'
});
function cbfunc(data) {
alert("OK");
}
And the "OK" message is never fired...
If available, use the jsonpCallback parameter in the call to $.ajax like:
jsonpCallback: "cbfunc",
Its description, from the jQuery API docs reads:
Specify the callback function name for a jsonp request. This value will be used instead of the random name automatically generated by jQuery.
The docs later go on to say:
It is preferable to let jQuery generate a unique name as it'll make it easier to manage the requests and provide callbacks and error handling. You may want to specify the callback when you want to enable better browser caching of GET requests.
However it is not advised to make use of this "preferable" behaviour when making use of YQL. Precisely why that approach is not ideal might make this answer far too verbose, so here is a link (from the YQL blog) detailing the problems with jQuery's preferred approach, making use of jsonpCallback and so on: Avoiding rate limits and getting banned in YQL and Pipes: Caching is your friend
You should let jQuery handle the callback by changing urlToWebservice to end in callback=?
The reason it's not working is because by specifying callback=cbfunc in the querystring generates a URL of the type:
http://query.yahooapis.com/...&callback=cbfunc&callback=jsonp1277417828303
Stripped out all uninteresting parts, but the URL contains two callback parameters. One of them is managed by jQuery, and the other one not. YQL only looks at the first callback parameter and returns a response wrapped around that.
cbfunc({"query":{...}});
However, there is no function named cbfunc in your script, so that's why you are getting the undefined error. jQuery created an implicit function named jsonp1277417828303 in the above example, and the response from YQL should instead have been:
jsonp1277417828303({"query":{...}});
for jQuery to act upon it, and return the response to your success callback which it never got to do.
So, as #SLaks suggested, remove the &callback=cbfuncfrom your URL, or replace it with &callback=? to let jQuery handle things.
See a working example.
You definitely should give jQuery-JSONP a try: http://code.google.com/p/jquery-jsonp/
Simplifies everything :)

Categories