Return GET value and store it in a variable in JS

Viewed 146

Please, note that I am a novice in JS, so expect any aberration you could possibly imagine.

That being said, I am trying to make a function in JS. This consists of three parts, but the ultimate goal is to populate a select with some outside data.

The first one is the GET. Here I call an external API asking for the total number of profiles. Everything seems to be working just fine, but whenever I do console.log(totalProfiles) outside the function, its value appears to be undefined. I tried adding a return at the end but it wasn't the solution.

var billingArray = [];
var billingProfiles = [];
var billingSelect = document.getElementById('billingSelect');
(function(){
    $.ajax({
        url: url,
        method: 'GET',
        crossDomain: true,
        withCredentials: true,
        dataType: 'JSON',
        headers: {
            'Authorization': 'Basic '+token,
        }
    })
    .done(function(response) { billingArray.push(response); var totalProfiles = billingArray[0]['total_count']; return totalProfiles; })
    .fail(function(jqXHR, textStatus, errorThrown) { console.log(textStatus); });
});

With totalProfiles, then I would call the same URL, this time to bring all the profiles to store them inside an array:

(function(totalProfiles){
    $.ajax({
        url: url+totalProfiles,
        method: 'GET',
        crossDomain: true,
        withCredentials: true,
        dataType: 'JSON',
        headers: {
            'Authorization': 'Basic '+token,
        }
    })
    .done(function(response) { billingProfiles.push(response); })
    .fail(function(jqXHR, textStatus, errorThrown) { console.log(textStatus); });
});

The last part would consists of populating the select by means of a for:

function(billingprofiles) {
    for (var i = 0; i < billingProfiles.length(); i++) {
        var billingProfileId = billingProfiles[i]["ngcp:billingprofiles"]["id"];
        var billingProfileName = billingProfile[i]["ngcp:billingprofiles"]["name"];

        var opt = document.createElement("option");
        opt.value() = billingProfileId;
        opt.textContent() = billingProfileName;
        dropdown.appendChild(opt);
    }
});

The issue here is that I don't know how to bring totalProfiles out of the function, so whenever the process reaches the second one, fails due to the variable being undefined.

This is the whole thing, and where you can expect those aberrations I talked about earlier. I thought this would work, but I am starting to suspect the way I intent to do it may be part of the issue too:

var billingArray = [];
var billingProfiles = [];
var billingSelect = document.getElementById('billingSelect');
var totalProfiles;

//Fetch total number of profiles
(function() {
  $.ajax({
    url: url,
    method: 'GET',
    crossDomain: true,
    withCredentials: true,
    dataType: 'JSON',
    headers: {
      'Authorization': 'Basic ' + token,
    }
  }).done(function(response) {
    billingArray.push(response);
    var totalProfiles = billingArray[0]['total_count'];
    return totalProfiles;
  }).fail(function(jqXHR, textStatus, errorThrown) {
    console.log(textStatus);
  });
})().done(function(totalProfiles) {
  $.ajax({
    url: url + totalProfiles,
    method: 'GET',
    crossDomain: true,
    withCredentials: true,
    dataType: 'JSON',
    headers: {
      'Authorization': 'Basic ' + token,
    }
  }).done(function(response) {
    billingProfiles.push(response);
  }).fail(function(jqXHR, textStatus, errorThrown) {
    console.log(textStatus);
  });
})().done(function(billingprofiles) {
  for (var i = 0; i < billingProfiles.length(); i++) {
    var billingProfileId = billingProfiles[i]["ngcp:billingprofiles"]["id"];
    var billingProfileName = billingProfile[i]["ngcp:billingprofiles"]["name"];

    var opt = document.createElement("option");
    opt.value() = billingProfileId;
    opt.textContent() = billingProfileName;
    billingSelect.appendChild(opt);
  }
}).fail(function(jqXHR, textStatus, errorThrown) {
  console.log(textStatus);
});

A few notes to better explain what and why I did it: 1. Before every .done I had to write () to avoid this error

(intermediate value).done is not a function

  1. The error I am getting right now happens at .done(function(totalProfiles) {:

Uncaught TypeError: Cannot read property 'done' of undefined

2 Answers

You're describing a chained series of asynchronous operations. Promises are a great fit for this. The Deferred object you get from ajax is a promise (now), so you can do it by returning a chain from each function to the next:

$.ajax({
    // ...
})
.then(billingArray => billingArray[0]['total_count']) // *** Return the count
.then(totalProfiles => $.ajax({ // *** Return the promise for the billing profiles
     // ...options using `totalProfiles`...
})
.then(billingProfiles => {
    // ...use the billing profiles here
})
.catch(() => {
    // ...handle/report failure
});

Note how each then handler transforms what passes through it, and in the situation where it needs information that's only avaialble asynchronously, it returns the promise from ajax; the result will be passed to the next then handler.

This will get you closer:

const billingSelect = document.getElementById('billingSelect');

// This is an IIFE that gets executed immediately on page load

(function(){
    $.ajax({
        url: url,
        method: 'GET',
        crossDomain: true,
        withCredentials: true,
        dataType: 'JSON',
        headers: {
            'Authorization': 'Basic '+token,
        }
    })

  // call your function in the done handler, and pass the data in

    .done(response => 
       build({billingprofiles: response, totalProfiles: response['total_count']}))
    .fail(function(jqXHR, textStatus, errorThrown) { console.log(textStatus); });
});

// your function gets called from the .done handler
// with the data it needs

function build({billingprofiles, totalProfiles}) {

    // use const everywhere. You rarely need variables.
    // use map to transform the data to get the shape you need

    // console.log(billingprofiles) // uncomment to check the shape

    const profiles = billingprofiles.map(profile => ({
        id: profile["ngcp:billingprofiles"]["id"],
        name: profile["ngcp:billingprofiles"]["name"]
    }))

   // console.log(profiles) // uncomment to check the shape

   // Use forEach when you need a side-effect, like DOM element creation
   // but only for that. For data transform, use map, filter and reduce.

   profiles.forEach(profile => {
     const opt = document.createElement("option");
     opt.value() = profile.id;
     opt.textContent() = profile.name;
     dropdown.appendChild(opt);
   })
}
Related