JMan
JMan

Reputation: 1805

Variable scope. Use a closure?

I'm trying to grab all the URLs of my Facebook photos. I first load the "albums" array with the album id's. Then I loop through the albums and load the "pictures" array with the photos URLs. (I see this in Chrome's JS debugger).

But when the code gets to the last statement ("return pictures"), "pictures" is empty.

How should I fix this? I sense that I should use a closure, but not entirely sure how to best do that. Thanks.

function getMyPhotos() {

        FB.api('/me/albums', function(response) {
            var data = response.data;
            var albums = [];
            var link;
            var pictures = [];


            // get selected albums id's
            $.each(data, function(key, value) {
                if ((value.name == 'Wall Photos')) { 
                        albums.push(value.id);
                }
            });
            console.log('albums');
            console.log(albums);

            // get the photos from those albums
            $.each(albums, function(key, value) {
                FB.api('/' + value + '/photos', function(resp) {
                     $.each(resp.data, function(k, val) {      
                            link = val.images[3].source;
                            pictures.push(link);
                     });
                });
            });

            console.log('pictures');
            console.log(pictures);
            return pictures;

        });
    }

Upvotes: 2

Views: 665

Answers (2)

Matt
Matt

Reputation: 41832

You're thinking about your problem procedurally. However, this logic fails anytime you work with asynchronous requests. I expect what you originally tried to do looked something like this:

var pictures = getMyPhotos();
for (var i = 0; i < pictures.length; i++) {
    // do something with each picture
}

But, that doesn't work since the value of 'pictures' is actually undefined (which is the default return type of any function without an actual return defined -- which is what your getMyPhotos does)

Instead, you want to do something like this:

function getMyPhotos(callback) {
    FB.api('/me/albums', function (response) {
        // process respose data to get a list of pictures, as you have already 
        // shown in your example

        // instead of 'returning' pictures, 
        // we just call the method that should handle the result
        callback(pictures);
    });
}

// This is the function that actually does the work with your pictures
function oncePhotosReceived(pictures){
    for (var i = 0; i < pictures.length; i++) {
      // do something with each picture
    }
};

// Request the picture data, and give it oncePhotosReceived as a callback.
// This basically lets you say 'hey, once I get my data back, call this function'
getMyPhotos(oncePhotosReceived);

I highly recommend you scrounge around SO for more questions/answers about AJAX callbacks and asynchronous JavaScript programming.

EDIT:

If you want to keep the result of the FB api call handy for other code to use, you can set the return value onto a 'global' variable in the window:

function getMyPhotos(callback) {
    FB.api('/me/albums', function (response) {
        // process respose data to get a list of pictures, as you have already 
        // shown in your example

        // instead of 'returning' pictures, 
        // we just call the method that should handle the result
        window.pictures = pictures;
    });
}

You can now use the global variable 'pictures' (or, explicitly using window.pictures) anywhere you want. The catch, of course, being that you have to call getMyPhotos first, and wait for the response to complete before they are available. No need for localStorage.

Upvotes: 3

hugomg
hugomg

Reputation: 69944

As mentioned in the comments, asynchronous code is like Hotel California - you can check any time you like but you can never leave.

Have you noticed how the FB.api does not return a value

//This is NOT how it works:
var result = FB.api('me/albums')

but instead receives a continuation function and passes its results on to it?

FB.api('me/albums', function(result){

Turns out you need to have a similar arrangement for your getMyPhotos function:

function getMyPhotos(onPhotos){
    //fetches the photos and calls onPhotos with the 
    // result when done

    FB.api('my/pictures', function(response){

        var pictures = //yada yada

        onPhotos(pictures);
    });
}

Of course, the continuation-passing style is contagious so you now need to call

getMyPhotos(function(pictures){

instead of

var pictures = getMyPhotos();

Upvotes: 0

Related Questions