Search code examples
javascriptreactjsspotifyes6-promisefetch-api

Spotify API, can't chain two API calls properly


I'm having trouble chaining these two API calls. So basically I'm trying to recreate the Spotify interface and individually, these calls actually work which is great!

I also have an 'onPlayClick' function that's basically the same function as 'onNextClick' as seen below, but with a different endpoint and is a PUT method instead of a POST method. That plays and sets the state of selectedSong correctly.

However, onNextClick calling updateCurrentlyPlaying does not work as intended. The intended functionality is that when the user clicks next, the song would go to the next track (which works!), AND THEN updates the track information on the UI.

But what ends up happening is that it FIRST updates the track information AND THEN switches track, so on the UI it's always "one track behind" in a sense and I do not know why the second API is getting ahead of the first one! :S Any help is greatly appreciated!

onNextClick = () => {
  const { deviceId, token } = this.state
  fetch(`https://api.spotify.com/v1/me/player/next?device_id=${deviceId}`, {
    method: 'POST',
    headers: { 'Authorization': 'Bearer ' + token },
  })
  .then(response => response.json())
  .then(this.updateCurrentlyPlaying())  
 }



updateCurrentlyPlaying(){
    const { deviceId, token } = this.state;

    let selectedSong;
    fetch(`https://api.spotify.com/v1/me/player/currently-playing?device_id=${deviceId}`, {
      method: 'GET',
      headers: { 'Authorization': 'Bearer ' + token },
    })
    .then(response => response.json())
    .then(data => {
      selectedSong = {
        name: data.item.name,
        duration: Math.round(data.item.duration_ms / 1000),
        artists: data.item.artists,
        album: data.item.album.name,
        image: data.item.album.images[0],
        id: data.item.id,
        uri: data.item.uri,
        offset: data.offset
      }
      this.setState({ selectedSong })
    })
  }

Solution

  • Updated Answer

    As discussed below, the original answer below is probably still relevant. However the first problem is the way this.updateCurrentlyPlaying is invoked. In writing .then(this.updateCurrentlyPlaying()), you are immediately invoking this.updateCurrentlyPlaying, which is passing undefined back into the Promise chain.

    Instead, you want something like:

    onNextClick = () => {
      const { deviceId, token } = this.state
      fetch(`https://api.spotify.com/v1/me/player/next?device_id=${deviceId}`, {
        method: 'POST',
        headers: { 'Authorization': 'Bearer ' + token },
      })
      .then(() => this.updateCurrentlyPlaying()) 
     }

    Original Answer

    My guess is that your code is fine, but Spotify isn't behaving the way you would expect it to. When you POST to me/player/next, the endpoint immediately responds code 204 indicating that it received your request.

    Indeed, that is what the Spotify API docs say:

    A completed request will return a 204 NO CONTENT response code, and then issue the command to the player.

    This means that your code sees the 204 and immediately goes on to call the Spotify API for the current track. This occurs before the passed command to the player noted above has been invoked by Spotify's player.

    I'm not very familiar with the Spotify API and I see this is in Beta - maybe they will provide a more elegant solution in the near future. Until then, your best bet might be to retry the second call (with a timeout limit) a few times until you confirm that the track has indeed changed.

    Here's a quick example how you might implement that:

      updateCurrentlyPlaying(priorSongId) {
        const {deviceId, token} = this.state;
    
        let selectedSong;
        let numTries = 0;
        const retryFetchUntilUpdate = () => {
          fetch(`https://api.spotify.com/v1/me/player/currently-playing?device_id=${deviceId}`, {
            method: 'GET',
            headers: {'Authorization': 'Bearer ' + token},
          })
            .then(response => response.json())
            .then(data => {
              if (data.item.id === priorSongId && numTries < 5) {
                numTries += 1;
                setTimeout(retryFetchUntilUpdate, 250)
              } else {
                selectedSong = {
                  name: data.item.name,
                  duration: Math.round(data.item.duration_ms / 1000),
                  artists: data.item.artists,
                  album: data.item.album.name,
                  image: data.item.album.images[0],
                  id: data.item.id,
                  uri: data.item.uri,
                  offset: data.offset
                }
                this.setState({selectedSong})
              }
            })
        }
        retryFetchUntilUpdate();
      }