1

I need some help. I'm quite new to Javascript, I'm currently trying to do something using two differents api: SongKick and Deezer. The idea is simple, on the page you can type your city, 1) I do a first request to the Songkick Api to get the ID of this city, 2) then with the ID, I do another request to get a list of concerts and I only take the name of the artist (20 maximum), 3) then I with the list of names I use the deezer Api to get the picture of the artist and a mp3 preview.

I've tried many ways but I can't access the data everywhere of course, and I don't know how to use callback cause there is too many things, if you can take a look that would be awesome.

Thanks!

artistsArray = [];
artistsArray2 = [];
arr = [artistsArray,[],[]];
var dispName;

var areaId;

function search(){

area = document.getElementById('band').value;



function songKickArea(callback){
    $.getJSON('http://api.songkick.com/api/3.0/search/locations.json?query=' + area + '&apikey=tIhpFoFn0dWpQ72A',function(data){
    var areaId = data['resultsPage']['results']['location'][0].metroArea.id;

    callback(areaId);
    });
    console.log("1 is done");


}



function findAreaId(callback){
    songKickArea(function(callback){
        console.log(callback);
    });
    $.getJSON("http://api.songkick.com/api/3.0/metro_areas/" + areaId + "/calendar.json?apikey=tIhpFoFn0dWpQ72A",function(data){
            for (var i=0; i<20 ; i++)
            {
            artistsArray.push(data['resultsPage']['results']['event'][i].performance[0].displayName);
        }
        callback(artistsArray);
        });
    console.log("2 is done");

}




function addInfos(callback){
    for (var i=0; i<20 ; i++)
    {
        DZ.api('/search?q=artist:' + '"'+ artistsArray[i]+'"' +'?limit=1', function(json){
    if(json.data[0]){
        artistsArray2.push({preview:json.data[0].preview, picture: json.data[0].artist.picture})
    }   
    });
}
console.log("3   is done");
    callback();
}   

function runSearchInOrder(callback) {
    songKickArea(function() {
        findAreaId(function() {

                addInfos(function() {
            console.log(areaId);
        });

      });
    });
}
runSearchInOrder(function(){console.log('finished')});

}

EDIT 09/17/2015

Thanks Vittore, I took a look at promises in JS and it's very interesting and perfect in my case. So now I'm here :

function songKickArea(areaId){
    area = document.getElementById('band').value;
    return $.getJSON('http://api.songkick.com/api/3.0/search/locations.json?query=' + area + '&apikey=XXXXXXX',function(data){
    });

}
function findAreaId(data){
    var areaId = data['resultsPage']['results']['location'][0].metroArea.id;
    return $.getJSON("http://api.songkick.com/api/3.0/metro_areas/" + areaId + "/calendar.json?apikey=XXXXXXX",function(data){
    });
}

function addInfos(data){

    for (var i=0; i<20 ; i++)
    {
        artistsArray.push(data['resultsPage']['results']['event'][i].performance[0].displayName);
        DZ.api('/search?q=artist:' + '"'+ artistsArray[i]+'"' +'?limit=1', function(json){
            if(json.data[0]){
                artistsArray2.push({preview:json.data[0].preview, picture: json.data[0].artist.picture})
            }
        });
    }
}

And I use this onClick:

songKickArea().then(findAreaId).then(addInfos).then(createList);

So everything is working fine, in addInfos my array artistsArray2 get all the infos I need from deezer (preview and picture). But now the next step is to create a list to display these artists (or tracks) so the next function is like this.

function createList(json){

    var html = '<ul>';

    for (var i=0; i<17; i++)
    {
        html+= '<li>';
        html += '<div class="picture">' + '<div class="player"><img  src="svg/play43.svg" ></div>'+ '<a href=' + artistsArray2[i].preview + '>' + '<img src=' + artistsArray2[i].picture + '>' + '</a>' +'</div>';
        html+= '<div class="arrow"><img src="css/svg/arrow487.svg"></div>';
        html+= '</li>';
    }
    html+= '</ul>';
    $('#results').append(html);
}

But here I have no idea how to pass the value of a full array from the last function to this one, could you help me ? Thanks a lot !

1 Answers1

0

UPDATE: Little clarification on multiple calls to the services and array of results.

You original code has addInfos method that iterates ( for loop ) through array and calling web service in that loop. What you want to do is to get results of each of those calls all together. While there are many ways of doing that, what I am showing you is using array.map to "map" each element of an array with data from step X to the promise returned by AJAX call. Let me give an example:

Say you have an array:

var artistIds = [6664009,6664010,6664011]

Now you can map it to promises:

var artistCalls = artistIds.map(function(id) {
     return $.getJson('~ get artists data service url ~' + id)
}

Which will give you array artistCalls each element of which will eventually have resolved promise with the data you need. While you can do all the crazy stuff with it, the easiest way of getting data from ALL calls is to use $.when helper method:

$.when(artistCalls).then(function(artists) {
      // here artists will array where each element is data returned by each AJAX call
})

Now if you want to render html to show all artists on the page you might have code like that:

function renderArtistHtml(artist) {
   return '<li>'
        += '<div class="picture"><div class="player"><img  src="svg/play43.svg" ></div><a href="' + artistsArray2[i].preview + '"><img src="' + artistsArray2[i].picture + '"></a></div>'
        += '<div class="arrow"><img src="css/svg/arrow487.svg"></div>'
        += '</div></li>';
}  

And a function that renders entire list:

function renderAllArtistsHtml(artists) {
   return '<ul>' + artists.map(renderArtistHtml) + '</ul>'
}

Now that you have it you can create whole chain of your functions together:

$(... my button selector ...).on('click',function(e) {

   var area = ... get area

   songKickArea(area)
        .then(findAreaId) // this thing returns promise that returns array
        .then(addInfos)   // this thing returns $.when(arr.map(...))
        .then(renderAllArtistsHtml) // this thing converts array of data from all calls from previous step to html layout
        .then(function(html) {  // this part just adds it to DOM
$('#results').append(html);
   });

})

Just answered similar question here.

Basically every ajax method in jquery returns promise ( and if your api does not return promise (like DZ.api ) you can wrap it in a $.deferred )

Once you return promises from your functions you can chain them:

function myajax1() {
   return $.getJson(...)
}

function myajax2(data) {
   return $.getJson(...)
}


myajax1().then(myajax2)

This will call myajax2 with data returned by myajax1 ajax call

You can chain it as many times as you want.

In case you need to wait for several you can use $.when:

$.when([myajax11(), myajax12()]).then(myajax2)

So closer to your actual code, you have 3 api calls:

  • songkick locations
  • songkick metro_areas
  • DZ.api

last one will require wrapping up in a promise, see example here: https://learn.jquery.com/code-organization/deferreds/examples/

Declare 3 functions:

function getLocations(area) {
   return $.getJson(....) // location query
}

function getMetroArea(data) {
   var areaId = data['resultsPage']['results']['location'][0].metroArea.id
   return $.getJson(...)  // metro query 
}

function getArtists(data) {
    var artist = data['resultsPage']['results']['event'][i].performance[0].displayName

    return DZAPIWraper(...)

}

And chain them:

getLocations(...).then(getMetroArea).then(getArtists)

In case you really need to do several calls for several artists in the last step, your code will look similar to:

 function getArtists(data) {

    var artists = getArtistsArrayFromMetro(data)

    var artistsCallbacks = artists.map(function(a) {
         return DZAPIWrapper(...)
    })
    return $.when(artistCallbacks)
 }

and that full chain is:

getLocations(...).then(getMetroArea).then(getArtists).then(function(artists) {
  // here artists going to be all artists data from all api calls to DZ.api

})
Community
  • 1
  • 1
vittore
  • 17,449
  • 6
  • 44
  • 82
  • Thanks a lot ! I edit my post with some new problems as I don't know how to pass a full array from a function to another, could you take a look ? Thanks – Maxime Béneteau Sep 17 '15 at 09:04
  • @MaximeBéneteau I already answered to your question in my answer - take a look at `getArtists` snippet. you are creating array of promises and returning `$.when(arrayOfPromises)`. The very last snipped is your full click handler body - inside last handler you are going to render your html. – vittore Sep 17 '15 at 13:24
  • I know, I tried but it's still very complicated to me (I'm a designer starting to code) I don't really understand the variable artists, or where to do my loop for. I understand the idea but can't find how to apply it to my code – Maxime Béneteau Sep 17 '15 at 14:17
  • Thank you very much that's perfect ! Just one last thing, how can I avoid an undefined error when I do a query to Deezer with an artist that doesnt exist on deezer ? – Maxime Béneteau Sep 18 '15 at 13:23
  • @MaximeBéneteau cheers. You need to wrap call to deezer api in a promise (i gave you a link with examples ) and need to handle error there, and return something like empty object. Then in the last step , instead of mapping whole `artists` array to html , you will filter it first ( google `mdn array.filter` ) to those that has data and then map. – vittore Sep 18 '15 at 14:59