Everless Drop 41
Everless Drop 41

Reputation: 112

YouTube Asynchronous function

I've been messing with the YouTube Javascript API recently, but I've run into a problem (as always!).

I need a function to return an array of information about a video:

 [0: Title, 1: Description, 2: Publish Date, 3: Thumbnail URL]

The function takes the id of a video then does a video.list with that id. Here is that function:

function getVidInfo(VidId){
    var vidRequest;
    var vidRequestResponse;
    var returnArray = new Array(4);

    vidRequest = gapi.client.youtube.videos.list({
        part: 'snippet',
        id: VidId
    });
    vidRequest.execute(function(response){
        if(response.pageInfo.totalResults != 0) {
            returnArray[0] = response.result.items[0].snippet.title;
            returnArray[1] = response.result.items[0].snippet.description;
            returnArray[2] = response.result.items[0].snippet.publishedAt;
            //Check for HD thumbnail
            if (response.result.items[0].snippet.thumbnails.maxres.url){
                returnArray[3] = response.result.items[0].snippet.thumbnails.maxres.url
            }
            else {
                returnArray[3] = response.result.items[0].snippet.thumbnails.standard.url;
            }
            console.log(returnArray); //Will log desired return array
        }
    });
    return returnArray; //Not returning desired array
}

As you can see from the comments the array is being set correctly however it's not returning that value.

What have I tried?

Notes

Full Code

index.html

<!DOCTYPE html>
<html>
<head>
    <title>YT Test</title>
    <!--My Scripts-->
    <script src="test.js"></script>
</head>

<body>
    <!-- Load google api last-->
    <script src="https://apis.google.com/js/client.js?onload=googleApiClientReady"> </script>
</body>
</html>

test.js

var apiKey = "[YOUR API KEY]"; //I did set this to my API key
var latestVidUrl;
var request;
var vidId;
var vidInfo;

function googleApiClientReady() {
    console.log("Google api loaded");
    gapi.client.setApiKey(apiKey);
    gapi.client.load('youtube', 'v3', function() {
        request = gapi.client.youtube.search.list({
            part: 'id',
            channelId: 'UCOYWgypDktXdb-HfZnSMK6A',
            maxResults: 1,
            type: 'video',
            order: 'date'
        });
        request.execute(function(response) {
            if(response.pageInfo.totalResults != 0) {
                vidId = response.result.items[0].id.videoId;
                //console.log(vidId);
                vidInfo = getVidInfo(vidId);
                console.log(vidInfo);
            }
        });
    }); 
}

function getEmbedCode(id){
    var baseURL = "http://www.youtube.com/watch?v="
    return baseURL + id.toString();
}

function getVidInfo(VidId){
    var vidRequest;
    var vidRequestResponse;
    var returnArray = new Array(4);

    vidRequest = gapi.client.youtube.videos.list({
        part: 'snippet',
        id: VidId
    });
    vidRequest.execute(function(response){
        if(response.pageInfo.totalResults != 0) {
            returnArray[0] = response.result.items[0].snippet.title;
            returnArray[1] = response.result.items[0].snippet.description;
            returnArray[2] = response.result.items[0].snippet.publishedAt;
            //Check for HD thumbnail
            if (response.result.items[0].snippet.thumbnails.maxres.url){
                returnArray[3] = response.result.items[0].snippet.thumbnails.maxres.url
            }
            else {
                returnArray[3] = response.result.items[0].snippet.thumbnails.standard.url;
            }
            console.log(returnArray); //Will log desired return array
        }
    });
    return returnArray; //Not returning desired array
}

Upvotes: 1

Views: 1603

Answers (2)

jlmcdonald
jlmcdonald

Reputation: 13667

The execute function is asynchronous; thus, it hasn't completed by the time you're returning the returnArray array, and so an empty array gets sent back instead (if you have the console open you'll see that's the case, where the empty array comes back and gets logged, and then a second or so later the logging within the callback happens). This is one of the biggest obstacles in asynchronous programming, and with the YouTube APIs it used to be that the only way around it was to nest your callbacks in multiple levels (i.e. don't have it as a separate function that returns a value) -- or what I like to affectionately term callback inception. So you could go that route (where you move all the code from your getVidInfo function up into the callback from the request where you get the ID), but that will get very messy ... and luckily the API client very recently introduced features that make solving this problem a whole lot easier -- the gapi client is now Promises/A+ compliant.

So basically, all request objects can now return a Promise object instead of utilize a callback function, and you can chain them all together so they all get processed and resolved in the order you need them to (note that this promise object does very slightly change the json structure of the response packet, where parameters such as pageInfo are children of the result attribute rather than siblings -- you'll see in the sample code below what I mean). This will also greatly simplify your code, so you could do something like this:

var apiKey = "[YOUR API KEY]";  

function googleApiClientReady() {
    console.log("Google api loaded");
    gapi.client.setApiKey(apiKey);
    gapi.client.load('youtube', 'v3', function() {
        var request = gapi.client.youtube.search.list({
            part: 'id',
            channelId: 'UCOYWgypDktXdb-HfZnSMK6A',
            maxResults: 1,
            type: 'video',
            order: 'date'
        }).then(function(response) {
            if(response.result.pageInfo.totalResults != 0) { // note how pageInfo is now a child of response.result ... this is because the promise object is structured a bit differently
                return response.result.items[0].id.videoId;
            }
        }).then(function(vidId) {
                return gapi.client.youtube.videos.list({
                        part: 'snippet',
                        id: vidId
                });
        }).then(function(response) { 
                var returnArray=Array();
                if(response.result.pageInfo.totalResults != 0) {
                        returnArray[0] = response.result.items[0].snippet.title;
                        returnArray[1] = response.result.items[0].snippet.description;
                        returnArray[2] = response.result.items[0].snippet.publishedAt;
                        //Check for HD thumbnail
                        if (response.result.items[0].snippet.thumbnails.maxres.url){
                                returnArray[3] = response.result.items[0].snippet.thumbnails.maxres.url;
                        }
                        else {
                                returnArray[3] = response.result.items[0].snippet.thumbnails.standard.url;
                        }
                }
                return returnArray;
        }).then(function(returnArray) {
                console.log(returnArray);
        });
  });
}

This architecture could also greatly help with error handling, as you could construct additional anonymous functions to pass as the 2nd argument in each then call to be executed when the API throws an error of some sort. Because each of the calls returns a promise, you can, in the final call, use returnArray however you need, and it will wait until all the pieces are resolved before executing.

Upvotes: 1

user3334871
user3334871

Reputation: 1361

I think the problem is that you are returning returnArray when it might not be handled yet. To clarify what I mean, Even though you have return returnArray at the end, the actual request is still be handled, but the code keeps going anyway. So when it finally gets a response, and handles the code, it writes it correctly to the log, but the function has already returned returnArray earlier. Without testing if this works, you could probably just add a polling function to wait until returnArray is not null, as long as you never expect it to be null. Maybe something like:

while(returnArray == null) {
    ; }
return returnArray;

I'll just edit this to clarify what I mean:

function getVidInfo(VidId){
var vidRequest;
var vidRequestResponse;
var returnArray = new Array(4);

vidRequest = gapi.client.youtube.videos.list({
    part: 'snippet',
    id: VidId
});
vidRequest.execute(function(response){
    if(response.pageInfo.totalResults != 0) {
        returnArray[0] = response.result.items[0].snippet.title;
        returnArray[1] = response.result.items[0].snippet.description;
        returnArray[2] = response.result.items[0].snippet.publishedAt;
        //Check for HD thumbnail
        if (response.result.items[0].snippet.thumbnails.maxres.url){
            returnArray[3] = response.result.items[0].snippet.thumbnails.maxres.url
        }
        else {
            returnArray[3] = response.result.items[0].snippet.thumbnails.standard.url;
        }
        console.log(returnArray); //Will log desired return array
    }
});

while(returnArray == null) {    //Create busy loop to wait for value
    ; }

return returnArray;

}

Upvotes: 1

Related Questions