Home > Back-end >  async function doesn't wait await fetch
async function doesn't wait await fetch

Time:07-07

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 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 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?

CodePudding user response:

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);

CodePudding user response:

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 looks okay up to the point where it side-effects the artists array. Let's change the 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) {
  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 => {
    let 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;
  });
  • Related