1

I have a function that must validate some artist from Spotify API but, when I run it, artists[] remains empty because the function is not waiting on the fetch: it fills the variable user without having set artists.

let artists = []

function setArtists(input) {
  artists.length = 0
  let parsedInput = input.value.split(",")
  parsedInput.forEach(artist => {
    validateArtist(artist)
  })
}

async function validateArtist(s) {
  let token = localStorage.getItem("Token")
  let url = "https://api.spotify.com/v1/search?type=artist&q=" + s
  console.log(url)
  await fetch(url, {
      "method": "GET",
      "headers": {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
        "Authorization": "Bearer " + token,
      }
    })
    .then(response => {
      if (response.status === 401) {
        refreshToken(s)
      }
      return response.json()
    })
    .then(searchedResults => searchedResults.artists.items.length != 0)
    .then(isArtist => {
      if (isArtist) {
        artists.push(s)
      }
    })
}

Here is where I call the function; I call it before so it can fill the artists variable:

setArtists(document.getElementById("artistiPreferiti"))
    var user = {
            username: document.getElementById("username").value,
            email: document.getElementById("email").value,
            password: document.getElementById("password").value,
            gustiMusicali: document.getElementById("gustiMusicali").value,
            artistiPreferiti: artists
    }    
 

How can I fix it?

3
  • 1
    forEach with async await never works like you expect. Use a regular for loop Commented Jul 6, 2022 at 15:32
  • 2
    Your setArtist() function will need to be async also, and it will need to await the validation function call. Something else will have to await the call to setArtist(), because only after the promise is satisfied will the array be updated as you expect. Commented Jul 6, 2022 at 15:35
  • @JaromandaX is correct. Commented Jan 11, 2023 at 14:42

2 Answers 2

3

You can use Promise.all() like:

const artists = ['first', 'second'];

const promises = artists.map(artist => fetch(url+artist));

Promise.all(promises)
.then(response => {
  // handle response
})
.catch(err);

or

const returnedData = await Promise.all(promises).catch(err);

Sign up to request clarification or add additional context in comments.

Comments

1

Looking at the pieces one at a time, setArtists must indeed by async and should probably run the validation currently (since the validations are not interdependent).

There's no need for the artists global in the containing scope. In fact, having it will encourage errors.

// clarifying... input is a comma-delimited string describing artists
// validate each one with spotify, and return an array of the valid artists
async function setArtists(input) {
  let parsedInput = input.value.split(",")
  let promises = parsedInput.map(validateArtist);
  let results = await Promise.all(promises);  // validate all of them at once
  // return just the valid inputs, not the nulls
  return results.filter(r => r);
}

The validateArtist method mixes async styles. Here it is with uniform style and with a clearer objective: to just validate and return an artist...

// given the params of an artist, lookup on spotify and return a
// a promise that resolves to the input artist if valid, null otherwise
async function validateArtist(s) {
  const token = localStorage.getItem("Token")
  const url = "https://api.spotify.com/v1/search?type=artist&q=" + s
  console.log(url)
  const response = await fetch(url, {
      "method": "GET",
      "headers": {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
        "Authorization": "Bearer " + token,
      }
  });
  if (response.status === 401) {
    refreshToken(s)
  }
  const searchedResults = await response.json();
  const isArtist = searchedResults.artists.items.length != 0;
  // notice, no side-effects here, resolve to the input artist or null
  return isArtist ? s : null;
}

Lastly, your caller must be async also, and await the setArtists result...

async function theCaller() {
  // notice - no need for a global. now it's a local here...
  let artists = await setArtists(document.getElementById("artistiPreferiti"))
  var user = {
    username: document.getElementById("username").value,
    email: document.getElementById("email").value,
    password: document.getElementById("password").value,
    gustiMusicali: document.getElementById("gustiMusicali").value,
    artistiPreferiti: artists
  } 
  // ...

As a side note, there might be valuable stuff in the artist array that spotify returns, some super set of the data you queried with. If you'd rather keep the results around, you can make up a policy to pick the first matching artist returned in the array of spotify matches...

// updating the functional comment:
// given the params of an artist, lookup on spotify and return a
// a promise that resolves to the first found artist or null if no matches are found

  .then(searchedResults => {
    let items = searchedResults.artists.items;
    return items.length ? items[0] : null;
  });

4 Comments

don't work, it still does not await the fetch
Just noticed that that method calling spotify incorrectly mixed await and then, most probably leaving you the impression that the fetch wasn't awaited. I'm certain it will await now. Also added error handling in case something else is going wrong in there.
thanks man now it works!!! But why with then it wasn't working?
Glad it helps. I think it was awaited, but the result was not returned.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.