I read a nice write up on on JSON and JSONP by Angus Croll. He provided a sample implementation of a safe JSONP library.
The library is concise and does the trick for a single connection, but I found it didn't handle multiple connections. It would overwrite a single global callback, so if you had connections of varying speeds, you would undoubtedly get errors. For instance, "Request 1" is very slow, and "Request 2" gets finished before it. When "Request 1" gets finished, it uses the callback for "Request 2". This actually happened the first time I loaded it up and ran it. Not good.
Here is a simple updated version that takes care of that particular problem.
(function(global) {
var evalJSONP = function(callback) {
return function(data) {
var validJSON = false;
if (typeof data == "string") {
try {validJSON = JSON.parse(data);} catch (e) {
}
} else {
validJSON = JSON.parse(JSON.stringify(data));
window.console && console.warn(
'response data was not a JSON string');
}
if (validJSON) {
callback(validJSON);
} else {
throw("JSONP call returned invalid or empty JSON");
}
}
};
var callbackCounter = ;
global.saferJSONP = function(url, callback) {
var fn = 'JSONPCallback_' + callbackCounter++;
global[fn] = evalJSONP(callback);
url = url.replace('=JSONPCallback', '=' + fn);
var scriptTag = document.createElement('SCRIPT');
scriptTag.src = url;
document.getElementsByTagName('HEAD')[].appendChild(scriptTag);
};
})(this);
To test it out:
var obamaTweets = "http://www.twitter.com/status/user_timeline/BARACKOBAMA.json?count=5&callback=JSONPCallback";
saferJSONP(obamaTweets, function(data) {console.log(data[].text)});
var reddits = "http://www.reddit.com/.json?limit=1&jsonp=JSONPCallback";
saferJSONP(reddits , function(data) {console.log(data.data.children[].data.title)});
If you are really worried about multiple global objects JSONPCallback_1
, JSONPCallback_2
, etc - you could store them all in an array instead. Like this:
(function(global) {
var evalJSONP = function(callback) {
return function(data) {
var validJSON = false;
if (typeof data == "string") {
try {validJSON = JSON.parse(data);} catch (e) {
}
} else {
validJSON = JSON.parse(JSON.stringify(data));
window.console && console.warn(
'response data was not a JSON string');
}
if (validJSON) {
callback(validJSON);
} else {
throw("JSONP call returned invalid or empty JSON");
}
}
};
var callbackCounter = ;
global.JSONPCallbacks = [];
global.saferJSONP = function(url, callback) {
var count = callbackCounter++;
global.JSONPCallbacks[count] = evalJSONP(callback);
url = url.replace('=JSONPCallback', '=JSONPCallbacks[' + count + ']');
var scriptTag = document.createElement('SCRIPT');
scriptTag.src = url;
document.getElementsByTagName('HEAD')[].appendChild(scriptTag);
};
})(this);