Getting Jiggy with JSONP

JSON with Padding (JSONP) has become a common technique for accessing cross-domain resources from the browser. In this tutorial you learn what JSONP is and how you can use it to retrieve data from cross-domain sources.

  • Difficulty: Beginner
  • Dojo Version: 1.6

Getting Started

Dojo's baked-in Ajax capabilities provide a simple, yet powerful interface to access resources dynamically. However, cross-domain security restrictions in the browser prevent you from making XHR requests to another domain. What to do? More modern browsers provide the ability to make cross-domain requests following the Cross-Origin Resource Sharing specification from the W3C. However, this is not yet available in all browsers (of course!), and there are a plethora of existing services that don't yet take advantage of this specification.

The answer to cross-domain communication is JSON with Padding, or JSONP. Bob Ippolito originally introduced the JSONP technique way back in 2005, and today many existing services from Google, Twitter, Facebook (to name a few) all offer API access to their services. Dojo's dojo.io.script module provides seamless access to JSONP resources, without some of the messy setup details.

What is this JSONP technique anyway? Unlike XHR, the browser doesn't prevent scripts from being loaded across domains. JSONP works by dynamically inserting a <script> element onto the page, and executing the response when it returns (as with any script). The trick is that the parameters are included as query parameters in the url, and the respone is a JSON message wrapped in a callback function. For example, a request may go to the endpoint?q=dojo&callback=callme, and its response will look like:

callme({id: "dojo", some: "parameter"})	

When the browser then evaluates the code in the script, it will call the callme() method—passing its data along. The local application, having defined the callme method, will then receive it. Note that this is essentially executing script from a third party; because you are executing script from a third party service, it is implied that you are trusting the third party service with your application. This is not to imply that JSONP is bad or should be avoided, only that its use should be limited to communication with trusted parties.

Using cross-domain resources with JSONP also reduces contention for connections to your applications' webservers. Browsers limit the number of requests that can be made to the server at one time. In the worst case, this is two concurrent connections on IE6. This defaults to 6-8 connections in newer browsers. When accessing a cross-domain resource, it does not count against the total number of connections to your server.

dojo.io.script automates the creation of the script element and callback methods, and provides you the familiar Deferred interface you are accustomed to from Dojo. Make sure to dojo.require("dojo.io.script");.

//include the script module
dojo.require("dojo.io.script");

//wait for the page and modules to be ready...
dojo.ready(function(){

	//Make a request to search twitter for #dojo
	dojo.io.script.get({
		url: "http://search.twitter.com/search.json",
		content: {q: "#dojo"},
		callbackParamName: "callback"
	});
});

This code follows the same basic pattern that you typically see with dojo.xhr. The only real oddity you'll notice is callbackParamName; this property tells Dojo which parameter the endpoint expects you to specify the callback function's name on (not the callback function name itself). This tends to vary a bit from service to service. From this point on, you can treat it like you would any other response. This code retrieves the most recent set of tweets related to #dojo. Let's flesh out the example a bit more, and show those results:

dojo.ready(function(){
	//first do the io script request
	dojo.io.script.get({
		url: "http://search.twitter.com/search.json",
		content: {q: "#dojo"},
		callbackParamName: "callback"
	}).then(function(data){

		//Loop through the data results and create an array containing
		//each tweet's text wrapped in an LI 

		var tweets=[];	
		dojo.forEach(data.results, function(item,idx){
			tweets.push("
  • " + item.text + "
  • "); }); //join the array of LIs together and make them the //innerHTML of the content holding node. dojo.byId("tweets").innerHTML = tweets.join(''); }); });

    That's pretty much all there is to it! To make it a bit fancier, here is a version that adds each tweet one at a time slowly:

    dojo.require("dojo.io.script");
    dojo.ready(function(){
    	//make the request just as before
    	dojo.io.script.get({
    		url: "http://search.twitter.com/search.json",
    		callbackParamName: "callback",
    		content: {q: "#dojo"}
    	}).then(function(data){
    		//we're only interested in data.results, so strip it off and return it
    		return data.results;
    	}).then(function(results){
    		//create an interval timer that will remove the first item in
    		//the array and render it.  Stop the loop when the results have been
    		//exhausted. 
    		var handle = setInterval(function(){
    			dojo.create("li", {innerHTML: results.shift().text},"tweets");	
    			if (results.length < 1){
    				clearInterval(handle);
    			}
    		}, 200);
    	});
    });
    
    View Demo

    The mechanism which drives JSONP (dynamically inserting a <script> tag) is unable to handle errors in the same way a standard Ajax request would. The browser never signals to the application when the script that is loading fails with an HTTP error (404, 500, etc.), and so the dojo.io.script callback never receives any signal for this either. To allow your application to proceed instead of waiting on this script to return forever, you can set a timeout property for the dojo.io.script request. If the callback hasn't been completed before the timeout is triggered, the Deferred will be rejected so your application can take appropriate action.

    Conclusion

    JSONP gives you access to a rich set of resources which you can creatively mashup with your own applications to create effective and interesting interfaces with ease. Most major web service providers provide some amount of access to their services using JSONP. Even within a single organization, accessing services via JSONP on a different subdomain can reduce contention for the limited number of concurrent connections some browsers allow to the server. Following the same patterns you are already used to with standard dojo.xhr requests, you should now be able to consume a cross-domain resource.

    If you are looking for practice, you could try to access the Flickr JSON API and display the resulting images. To help you get started, here is a Flickr URL which will return Dojo Toolkit-tagged images: http://api.flickr.com/services/feeds/photos_public.gne?tags=dojotoolkit&lang=en-us&format=json

    For further reading

    Error in the tutorial? Can’t find what you are looking for? Let us know!