Daniel B
Daniel B

Reputation: 8879

Sequential observables in promise-like style

Exploring observables, I've run into a problem where I need to execute functions only after another function has been completed. All functions are async operations that use the Http service in @angular/http.

The call chain should be

  1. Check if playlist exists. If playlist doesn't exist, create it.
  2. GET recommended tracks
  3. Add recommended tracks to playlist

Normally, I would've written this with promises and have a chain that would look something like

this.checkIfPlaylistExists()
    .then((exists) => {
        if(!exists) {
            return this.createPlaylist();
        }
    })
    .then(() => this.getRecommendedTracks(seedTracks)) //resolves tracks
    .then(this.addRecommendedTracksToPlaylist)
    .then(doSomethingElse)
    .catch();

My confusion is how I would write the code using observables instead. What I have so far isn't working 100%, and looks like this

  createRecommendedTracksPlaylist(seedTracks: Track[]) {
    this.checkIfPlaylistExists()
      .map(exists => {
        if (!exists) {
          return this.createPlaylist();
        }
      })
      .map(() => this.getRecommendedTracks(seedTracks))
      .map((tracks) => this.addRecommendedTracksToPlaylist(tracks)) //<--- if I comment out this line, 'getRecommendedTracks' is executed and prints out response
      .mergeAll()
      .subscribe();
  }

The functions called

getRecommendedTracks(seedTracks: Track[]) {
    seedTrackIds = seedTracks.map((track) => track.id).join(',');
    const recommendationsUrl = `https://api.spotify.com/v1/recommendations?seed_tracks=${seedTrackIds}`;
    return this.http.get(recommendationsUrl, options).map((res) => {
      const recommendedTracks = res.json().tracks as Array<Track>;
      console.log('Getting recommended tracks'); //Is only run if I don't run 'addRecommendedTracksToPlaylist' function
      console.log(recommendedTracks);
      return recommendedTracks;
    });
}

addRecommendedTracksToPlaylist(tracksToAdd) {
    console.log('Going to add tracks..');
    console.log(tracksToAdd); //Prints out 'Observable {_isScalar: false, ... }'
    const options = this.getAuthHeader();
    const playlistUri = '2OGx5l1ItjzMsQdQ0Hec6g';
    const addTracksUrl = `https://api.spotify.com/v1/users/${this.userId}/playlists/${playlistUri}/tracks`;
    return this.http.get(addTracksUrl, options);
}

Edit: Adding the checkIfPlaylistExists code (please not that the playlist currently always exists)

  private checkIfPlaylistExists() {
    const options = this.getAuthHeader();
    const playlistUrl = `https://api.spotify.com/v1/users/${this.userId}/playlists`;
    return this.http.get(playlistUrl, options).map((res) => {
      const playlists = res.json().items as Array<any>;
      return playlists.some((e, i, a) => {
        return e.name.indexOf(this.playlistTitle) > -1;
      })
    });
  }

So to sum it up

Upvotes: 3

Views: 1468

Answers (1)

CozyAzure
CozyAzure

Reputation: 8468

Just change your .map() to.flatMap() and you are good to go:

createRecommendedTracksPlaylist(seedTracks: Track[]) {
    this.checkIfPlaylistExists()
        .flatMap(exists => {
            if (!exists) {
                return this.createPlaylist();
            }
            return Observable.of(1);
        })
        .flatMap(() => this.getRecommendedTracks(seedTracks))
        .flatMap((tracks) => this.addRecommendedTracksToPlaylist(tracks))
        .subscribe();
}

You will need to return a dummy Observable if the playlist doesnt exist, because flatMap expect a return of Observable. Observable.empty() won't work because it just end the stream.

Upvotes: 2

Related Questions