Search code examples
jquerycallbackscopejsonphoisting

JSONP Callback variable hoisting, scoping anomoly


I have a global array I want to populate with URLs to JPEGs at Flickr. I intend to pass this array around between functions.

When I try to read the contents of the array (or console.log from main.js:40) from within my JASONP callback function it appears to be empty. Yet from the browser console it seems fine (screen shot). My JASONP callback seems able to array.push() but can't read array[i].src.

What's going on here?

screenshot of console

I seem to be misunderstanding how variables are scoped from JASONP callbacks. Can someone enlighten me (in super-noob laymen's terms) as to what's happening here and show me what this should look like?

JSFiddle here - http://jsfiddle.net/Nyquist212/7gtxetxj/

function UrlExists(url, callback) {
    var http = new XMLHttpRequest();
    http.open('HEAD', url);
    http.onreadystatechange = function() {
        if (this.readyState == this.DONE) {
            callback(this.status = 200);
        }
    };
    http.send();
};

var images = []; // Store imgs in here

$.getJSON("https://api.flickr.com/services/rest/?method=flickr.photos.search&jsoncallback=?",
      { 
          api_key : "da2015d88b5eab3b3d7d989c13c4d430",
          content_type : 1,
          text : "landscape, beach, coast, surf, black, white",
          format : "json",
          per_page : 10
      },

      function jsonFlickrApi (results) { // my JSONP callback func 

            for (var i=0; i < results.photos.photo.length; i++) {
                var farm    = results.photos.photo[i].farm;
                var id      = results.photos.photo[i].id;
                var server  = results.photos.photo[i].server;
                var secret  = results.photos.photo[i].secret;
                var furl    = "https://farm" + farm + ".staticflickr.com/" + server + "/" + id + "_" + secret + "_m" + ".jpg";

                if (!UrlExists(furl, mycallback)) {
                    function mycallback(results){
                        images.push({"src":furl}); 
                        };
                    $("<img />").attr("src", furl).appendTo("#one");              // This works...
                    //$("<img />").attr("src", images[i].src).appendTo("#one");   // Why wont this work?
                }; 
            }; // End for 

            console.log(images); // Returns empty!?!

      } // End jsonFlickrApi
); // End getJSON

Thank you.


Solution

  • This code:

    if (!UrlExists(furl, mycallback)) {
        function mycallback(results) {
            images.push({
                "src": furl
            });
        };
        $("<img />").attr("src", furl).appendTo("#one"); // This works...
        //$("<img />").attr("src", images[i].src).appendTo("#one");   // Why wont this work?
    };
    

    never calls that mycallback function, and thus nothing is ever added to images. (Also note that you don't put ; after the closing } of the block attached to a constrol flow statement like if.)

    Assuming UrlExists expects you to pass it a callback for some reason and calls that callback asynchronously, you'll need to actually pass the function to it, and you'll need to ensure that when the function is called, it's called with the right furl (since if it's called asynchronous, the furl you're currently using will have been changed before the callback is called). The same would be true of i in your commented out code. With those assumptions in place, you can do this:

    function jsonFlickrApi(results) { // my JSONP callback func 
    
        results.photos.photo.forEach(function(photo) {
            var furl = "https://farm" + photo.farm +
                       ".staticflickr.com/" +
                       photo.server + "/" +
                       photo.id + "_" +
                       photo.secret + "_m" + ".jpg";
            UrlExists(furl, function() {  // I assume this is called if the URL does exist, and that that's what you want 
                images.push({
                    "src": furl
                });
                $("<img />").attr("src", furl).appendTo("#one");
            });
        });
    
        // Note that `images` will still be empty here, waiting for the callbacks
        // from UrlExists
    
    } // End jsonFlickrApi
    

    Now, nothing in the UrlExists callback is relying on something that will change before the callback occurs, because a new, separate furl is created for every call to the forEach callback.