Reputation: 112
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?
vidRequest.execute()
vidRequest.execute()
undefined
)Notes
getVidInfo()
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
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
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