0

I'm learning Node JS as well as how to access an API's information, and I've come across a problem where when I push values to an array, the info never gets added globally.

So let's say I have var albIds= new Array(5), the values never get added. Through looking at this and this I now know that the issue is that the information never gets into the array by the time I call it, due to the synchronous code being done first, however, I am confused at how to make the synchronous wait for the asynchronous to complete.

my code so far.

//prints out albums 1-5 id's
var id = '3TVXtAsR1Inumwj472S9r4';
var albIds= new Array(5);
async function getAlbums(i) {

var authOptions = {
  url: 'https://accounts.spotify.com/api/token',
  headers: {
    'Authorization': 'Basic ' + (new Buffer.from(client_id + ':' + client_secret).toString('base64'))
  },
  form: {
    grant_type: 'client_credentials'
  },
  json: true
};

request.post(authOptions, function(error, response, body) {
  if (!error && response.statusCode === 200) {

    // use the access token to access the Spotify Web API
    // const querystring = require('querystring');

const data = {
  market: "US",
  limit: "5"
};
const  q = querystring.stringify(data);
    var token = body.access_token;
    var options = {
      url: 'https://api.spotify.com/v1/artists/'+id+'/albums?'+q,
      headers: {
        'Authorization': 'Bearer ' + token
      },
      json: true
    };
  request.get(options, function(error, response, body)
     {
  for(var i = 0; i<5;i++)
      {
       albIds.push(body.items[i].id);
      }
});
}

})
};

async function fetchUsers()
{
  for(var i = 0;i<5;i++)
  {
    await getAlbums(i);
    console.log(albIds[i]);
  }
}
fetchUsers();

Any help appreciated. For reference, I'm using Spotify's API

1 Answers1

1

FYI, albIds is NOT a global. It's a module scoped variable.

Then, you need to understand that await ONLY does something useful if you are awaiting a promise that is connected to the completion of your asynchronous operation. That is NOT the case for your getAlbums() function. While it's declared async so it does return a promise, that promise is not connected at all to the completion of your asynchronous operations.

Instead, the promise completes, long before you've put any data into the albIds array. Thus, you think the array is empty (because nothing has yet been put into it when you're trying to use it).

Here would be my suggestion:

const rp = require('request-promise');

//prints out albums 1-5 id's
const id = '3TVXtAsR1Inumwj472S9r4';

async function getAlbums(i) {
    const albIds = new Array(5);

    const authOptions = {
        url: 'https://accounts.spotify.com/api/token',
        headers: {'Authorization': 'Basic ' + (new Buffer.from(client_id + ':' + client_secret).toString('base64'))},
        form: {grant_type: 'client_credentials'},
        json: true
    };

    const body = await rp.post(authOptions, function(error, response, body) {

    const data = {
        market: "US",
        limit: "5"
    };
    const q = querystring.stringify(data);
    const token = body.access_token;
    const options = {
        url: 'https://api.spotify.com/v1/artists/' + id + '/albums?' + q,
        headers: { 'Authorization': 'Bearer ' + token },
        json: true
    };
    let result = await rp.get(options);

    for (let j = 0; j < 5; j++) {
        albIds.push(result.items[j].id);
    }
    // let resolved value be the array
    return albIds;
};

async function fetchUsers() {
    for (let i = 0; i < 5; i++) {
        const albiIds = await getAlbums(i);
        console.log(albIds[i]);
    }
}
fetchUsers();

Summary of Changes:

  1. Switch to the request-promise library that uses promises to communicate results and checks for a 2xx status automatically. This is much more compatible with an async function and we can use await with it.
  2. Make the albIds be the resolved value of getAlbums() rather than a higher scoped, shared variable (much more immune to concurrency issues and it ensures you can't attempt to use it before it's ready).
  3. In fetchUsers(), use the albIds that comes from the promise returned by getAlbums().
  4. Change all variables to let or const as appropriate (no need to use var here at all).
  5. Use await on the request-promise calls to simplify the code flow and to be compatible with the promise that getAlbums() is returning.

FYI, if this is fairly new code, I would stop using the request or request-promise libraries as they are officially deprecated for new development. While they will be maintained, they will not be getting new features added to them over time. You can read why this is happening, but basically they've been around so long and node.js has changed so much during that time that they've become a bit of a mess to maintain and move forward. But, there are so many users that they can't really make the breaking changes to the API that they would have to in order to really continue to move it forward (like make it entirely promise-driven). And, since there are a number of good alternatives that have a more modern design, they've decided to turn the future over to the other alternatives. I'm personally using got() for most of my uses, but there are numerous other alternatives to choose from.

See Should I use the 'request' module for a new project? for more info.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Wow this made the concept click, thanks so much it finally makes sense! Yea this is a new project so I'll look into ```got()``` . Thanks so much again! – itzknockout Mar 14 '20 at 00:56