Skip to main content Skip to secondary navigation Accessibility Feedback

Working with JSONP

In petfinderAPI4everybody.js, I fetch data from Petfinder’s API on the client side using only native JavaScript—no jQuery needed.

Today, I want to share my code and talk about my approach.

Psst... I just launched a new guide to ditching jQuery for vanilla JS. Buy it today and save 25%! →

The Technique #

To use API data hosted on a domain other than your own, you need to use a technique called JSONP. From Wikipedia:

JSONP or “JSON with padding” is a communication technique used in JavaScript programs running in web browsers to request data from a server in a different domain, something prohibited by typical web browsers because of the same-origin policy. JSONP takes advantage of the fact that browsers do not enforce the same-origin policy on <script> tags.

Here’s how it works: The API request URL is set as the src of a <script> tag, which gets appended to the DOM. That data is then passed into a callback method on load. The callback is included in the request url as ?callback=callbackMethodName;

The Code #

/**
 * Get JSONP data for cross-domain AJAX requests
 * @private
 * @link http://cameronspear.com/blog/exactly-what-is-jsonp/
 * @param  {String} url      The URL of the JSON request
 * @param  {String} callback The name of the callback to run on load
 */
var loadJSONP = function ( url, callback ) {

    // Create script with url and callback (if specified)
    var ref = window.document.getElementsByTagName( 'script' )[ 0 ];
    var script = window.document.createElement( 'script' );
    script.src = url + (url.indexOf( '?' ) + 1 ? '&' : '?') + 'callback=' + callback;

    // Insert script tag into the DOM (append to <head>)
    ref.parentNode.insertBefore( script, ref );

    // After the script is loaded (and executed), remove it
    script.onload = function () {
        this.remove();
    };

};

And here’s an example using the Petfinder API:

var logAPI = function ( data ) {
    console.log( data );
}

getJSONP( 'http://api.petfinder.com/shelter.getPets?format=json&key=12345&shelter=AA11', 'logAPI' );

Client-Side Performance #

Loading data from APIs can be slow, so you want to minimize the amount of times you have to request data.

With petfinderAPI4everybody.js, I decided to store API data in localStorage so that after initial page load, the data is immediately available for use on each subsequent page load. I added an expiration date so that after a certain period of time, new data will be fetched to replace it.

The Code #

Here’s the code that makes it all work:

var getJSONP = ( function( window, document, undefined ) {

    // Set Variables
    var getJSONP = {}; // Object for public APIs
    var settings = {}; // Object for settings
    var url, localAPI;

    /**
     * Get JSONP data for cross-domain AJAX requests
     * @private
     * @link http://cameronspear.com/blog/exactly-what-is-jsonp/
     * @param  {String} url      The URL of the JSON request
     * @param  {String} callback The name of the callback to run on load
     */
    var loadJSONP = function () {

        // Create script with url and callback (if specified)
        var ref = window.document.getElementsByTagName( 'script' )[ 0 ];
        var script = window.document.createElement( 'script' );
        script.src = url + (url.indexOf( '?' ) + 1 ? '&' : '?') + 'setAPIData';

        // Insert script tag into the DOM (append to <head>)
        ref.parentNode.insertBefore( script, ref );

        // After the script is loaded (and executed), remove it
        script.onload = function () {
            this.remove();
        };

    };

    /**
     * Save remote API data to localStorage and set variable for use
     * @public
     * @param {Object} data API data object from Petfinder
     */
    getJSONP.setAPIData = function ( data ) {

        // Check for error codes
        // Replace with relevant name and code from your API
        if ( data.someCode === '000' ) {
            console.log('Unable to get data from the API. Using expired localStorage data instead.');
            run();
            return;
        }

        // Save API Data to localStorage with expiration date
        var expirationMS = parseInt( settings.expiration, 10 ) * 60 * 1000;
        localAPI = {
            data: data,
            timestamp: new Date().getTime() + expirationMS
        };
        localStorage.setItem( settings.localAPIid, JSON.stringify(localAPI) );

        // Run methods after data loads
        run();

    };

    /**
     * Get API data from localStorage
     * @private
     */
    var getAPIData = function () {

        // Get API data from localStorage
        localAPI = JSON.parse( localStorage.getItem( settings.localAPIid ) );

        // If local data exists and hasn't expired, use it
        if ( localAPI ) {
            if ( new Date().getTime() < localAPI.timestamp ) {
                run();
                return;
            }
        }

        // If local data doesn't exist or has expired, get fresh data
        getJSONP( url );

    };

    /**
     * Do stuff after API is loaded
     * @private
     */
    var run = function () {

        // If no API data is available, log error
        if ( !localAPI ) {
            console.log( 'Unable to retrieve data from the API or localStorage.' );
            return;
        }

        // Do stuff here...
        console.log( localAPI );

    };

    getJSONP.init = function ( url, options ) {

        // Check for localStorage support before running
        if ( !window.localStorage ) return;

        // If no URL is supplied, quit
        if ( !url ) return;

        // Merge options with defaults
        settings.expiration = options.expiration || 60;
        settings.localAPIid = options.localAPIid || 'localJSONP';

        // Get API data
        getAPIData();

    };

    // Return public methods
    return getJSONP;

})( window, document );

And here’s an example using the Petfinder API:

getJSONP.init( 'http://api.petfinder.com/shelter.getPets?format=json&key=12345&shelter=AA11' );
If you liked this article, you might also like Ditching jQuery, my new beginner's guide to vanilla JavaScript. Buy it today and save 25%! →
  • More articles on...
  • Code

Have any questions or comments about this post? Email me at chris@gomakethings.com or contact me on Twitter at @ChrisFerdinandi.

Get the Weekly Digest