0

So I'm trying to create a small app for myself using the Last.fm API.

I stuck now on the problem that when I request the Track Duration I can successfully log it into the console and append it from the Dev Tools but in the first run on the DOM itself it just spits out an undefined or NaN (if I use parseFloat) .

Is it possible that my ajax request to slow is, to fetch the data from the API and then also to render it on the DOM? If yes, what would be the possible solution for it? Or a push in the right direction where to look would be sweet.

Here is my jQuery Code:

var Trackster = {};
const API_KEY = '1234';
var trackResults;
var trackDuration;

$(document).ready(function() {
    $("#submitButton").click(function() {
        Trackster.searchTracksByTitle($("#inputField").val());
        $('#detailContainer').empty();
    })
})

/*
  Given an array of track data, create the HTML for a Bootstrap row for each.
  Append each "row" to the container in the body to display all tracks. 
*/
Trackster.renderTracks = function(tracks) {
     for (var i = 0; i <= (trackResults.length)-1; i++) {

            var mediaAlbumArt = trackResults[i].image[1]["#text"];

            $.ajax({
                            url: 'https://ws.audioscrobbler.com/2.0/?                                
                                  method=track.getInfo&
                                  api_key='+API_KEY+'&artist=' 
                                  + trackResults[i].artist 
                                  + '&track=' 
                                  + trackResults[i].name 
                                  + '&format=json',
                            dataType: 'jsonp',
                            success: function(d) {
                                console.log("Success", d.track.duration);
                                trackDuration = d.track.duration;
                            },
                            error: function(err) {
                                console.log("Error", err);
                            }

            })

        var $tracks =   '<div id="song-details" class="container-fluid">'+
                          '<div class="row align-items-center h-100">'+                                 
                            '</div>'+
                            '<div class="col-md-1">'+
                              '<span>' + parseFloat(i+1) + '</span>'+
                            '</div>'+
                            '<div class="col-md-3">'+
                              '<span>' + trackResults[i].name + '</span>'+
                            '</div>'+
                            '<div class="col-md-2">'+
                              '<span>' + trackResults[i].artist + '</span>'+
                            '</div>'+
                            '<div class="col-md-2">'+
                              '<img src="' + mediaAlbumArt + '" />'+
                            '</div>'+
                            '<div class="col-md-1">'+
                              '<span>' + trackResults[i].listeners + '</span>'+
                            '</div>'+
                            '<div class="col-md-1" id="duration">'+
                                '<span>' + trackDuration  + '</span>'
                            '</div>'+ 
                        '</div>'+
                       '</div>';                       

                       $('#detailContainer').append($tracks);

     }    
};

Trackster.searchTracksByTitle = function(title) {



    $.ajax({
                        url: 'https://ws.audioscrobbler.com/2.0/? 
                              method=track.search
                              &track='+($("#inputField").val())
                              +'&api_key='+API_KEY+'
                              &format=json',

                    dataType: 'jsonp',
                    success: function(data) {
                        console.log("Success!", data);
                        trackResults = data.results.trackmatches.track;
                        Trackster.renderTracks();                                           
                    },
                    error: function(e) {
                        console.log("Error!", e);
                    }
                })  
    };

I included the ajax request in the loop as I need to know the duration of every song and that's the solution I came up with.

And here is the App running on GitHub https://drood87.github.io/start/

Hope someone can give me a hint or two. Thanks <3

drood87
  • 39
  • 7
  • Possible duplicate of [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – CBroe Apr 03 '18 at 08:55

2 Answers2

1

You are mixing synchronous operation with async operation. $.ajax works asynchronously, and the data which returns from that call isn't immediately available for you to process. You can move your append inside the AJAX success function to make the script use the duration data.

Or,

You can relate the data with the trackid, and use something like this:

When creating the row which contains the data:

'<div id="song-details-' + trackResults[i].mbid + '" class="container-fluid">' +
...
'<div class="col-md-1 duration">'

BTW, you can't have elements which have the same ID in the same document. It will break things up.

Then on the AJAX side;

$.ajax({success(data){
   $("#song-details-"+data.track.mbid).find(".duration").text(data.track.duration);
});

This way you can fill the duration row asynchronously.

Taha Paksu
  • 15,371
  • 2
  • 44
  • 78
  • Thank you for your answer. Good that you're telling me about the `id` issue. Haven't even thought about it that it gets multiplied with the loop iterations. Thanks for that! – drood87 Apr 03 '18 at 09:47
0

Your ajax is not slow, your code is not correctly organized

Look at your track info getting ajax:

$.ajax({
            url: 'https://ws.audioscrobbler.com/2.0/?                                
                  method=track.getInfo&
                  api_key='+API_KEY+'&artist=' 
                  + trackResults[i].artist 
                  + '&track=' 
                  + trackResults[i].name 
                  + '&format=json',
            dataType: 'jsonp',
            success: function(d) {
                console.log("Success", d.track.duration);
                trackDuration = d.track.duration;
            },
            error: function(err) {
                console.log("Error", err);
            }
        })

Here in your success function you're setting the trackDuration and then you're creating some HTML out of it. You see, in javascript you cannot rely that code after your ajax call will be executed before success callback function of ajax. To ensure the execution order you need to put your handler code in success function. Please look at reworked renderTracks function

Trackster.renderTracks = function(tracks) {
let detailContainer = $('#detailContainer');
for (var i = 0; i <= (trackResults.length)-1; i++) {
    var mediaAlbumArt = trackResults[i].image[1]["#text"];
    ((i) => $.ajax({
        url: 'https://ws.audioscrobbler.com/2.0/?                                
              method=track.getInfo&
              api_key='+API_KEY+'&artist=' 
              + trackResults[i].artist 
              + '&track=' 
              + trackResults[i].name 
              + '&format=json',
        dataType: 'jsonp',
        success: function(d) {
            console.log("Success", d.track.duration);
            let trackDuration = d.track.duration;

            var $tracks =   '<div id="song-details" class="container-fluid">'+
                  '<div class="row align-items-center h-100">'+                                 
                    '</div>'+
                    '<div class="col-md-1">'+
                      '<span>' + parseFloat(i+1) + '</span>'+
                    '</div>'+
                    '<div class="col-md-3">'+
                      '<span>' + trackResults[i].name + '</span>'+
                    '</div>'+
                    '<div class="col-md-2">'+
                      '<span>' + trackResults[i].artist + '</span>'+
                    '</div>'+
                    '<div class="col-md-2">'+
                      '<img src="' + mediaAlbumArt + '" />'+
                    '</div>'+
                    '<div class="col-md-1">'+
                      '<span>' + trackResults[i].listeners + '</span>'+
                    '</div>'+
                    '<div class="col-md-1" id="duration">'+
                        '<span>' + trackDuration  + '</span>'
                    '</div>'+ 
                '</div>'+
               '</div>';                       

           detailContainer.append($tracks);
        },
        error: function(err) {
            console.log("Error", err);
        }
    }))(i)
}}; 

I've put your track HTML creation logic in success callback function of ajax - it ensures that HTML creation will happen on success of your ajax

Pay attention that I've created let detailContainer = $('#detailContainer'); before the loop and do the element lookup only 1 time - it's an optimization you should always use

Also look how I wrapped your ajax function with ((i) => $.ajax ... )(i). This is called IIFE and it helps you to save the value of i exclusively in each ajax call. You can also use let i instead var i and IFFY

davidluckystar
  • 928
  • 5
  • 15
  • Thank you, that worked! I knew I had some logical mistakes in there but could not see it! Also thank you for the tips regarding IIFE, haven't heard that beofre, gonna read more into it. Weirdly it messed up my HTML, but that will be an easy bugfix I think! – drood87 Apr 03 '18 at 09:45
  • glad to help :) – davidluckystar Apr 03 '18 at 10:02