0

There are 2 functions that I need to run one-by-one: getUserPlaylists (receives Playlists) and getPlaylistTracks (receives Tracks for provided Playlist).

One response can have up to 50 tracks, so I need to use PageToken if I want to get the rest tracks. The problem is that I can not make a recursive function getPlaylistTracks to wait till the recursion is done.

function getPlaylistsWithTracks () {
  return new Promise((resolve, reject) => {
    getUserPlaylists()
      .then(function (playlists) {

        playlists.forEach(
          async function (playlistObj) {
            await getPlaylistTracks(playlistObj).then(function (tracks) {
              playlistObj['tracks'] = tracks
            })
          })

        console.log('resolve')
        resolve(playlists)
      })
  })
}

function getPlaylistTracks (playlistObj, pageToken) {
  return new Promise((resolve, reject) => {

    let playlistTracks = []

    let requestOptions = {
      'playlistId': playlistObj['youtubePlaylistId'],
      'maxResults': '50',
      'part': 'snippet'
    }

    if (pageToken) {
      console.log('pageToken:', pageToken)
      requestOptions.pageToken = pageToken
    }

    let request = gapi.client.youtube.playlistItems.list(requestOptions)

    request.execute(function (response) {
      response['items'].forEach(function (responceObj) {
        let youtubeTrackTitle = responceObj.snippet.title

        if (youtubeTrackTitle !== 'Deleted video') {
          let youtubeTrackId = responceObj.snippet.resourceId.videoId

          playlistTracks.push({
            youtubePlaylistId: playlistObj.playlistId,
            youtubePlaylistTitle: playlistObj.playlistTitle,
            youtubeTrackId: youtubeTrackId,
            youtubeTrackTitle: youtubeTrackTitle,
          })
        }

      })

      // Here I need to wait a bit
      if (response.result['nextPageToken']) {
        getPlaylistTracks(playlistObj, response.result['nextPageToken'])
          .then(function (nextPageTracks) {
            playlistTracks = playlistTracks.concat(nextPageTracks)
          })
      }

    })

    resolve(playlistTracks)

  })
}

getPlaylistsWithTracks()

In my case in console I see the next:

> resolve
> pageToken: 123
> pageToken: 345

but, I want to see resolve the last.

How to wait till recursion is executed?

TitanFighter
  • 4,582
  • 3
  • 45
  • 73

1 Answers1

2

Avoid the Promise constructor antipattern, and (don't) use forEach with async functions properly.

Furthermore, there is nothing special about recursion. It's like any other promise-returning function call that you would want to wait for - put it in your then chain or await it. (The latter is considerably easier).

async function getPlaylistsWithTracks() {
  const playlists = await getUserPlaylists();
  for (const playlistObj of playlists) {
    const tracks = await getPlaylistTracks(playlistObj);
    playlistObj.tracks = tracks;
  }
  console.log('resolve')
  return playlists;
}

async function getPlaylistTracks(playlistObj, pageToken) {
  let playlistTracks = []
  let requestOptions = {
    'playlistId': playlistObj['youtubePlaylistId'],
    'maxResults': '50',
    'part': 'snippet'
  }
  if (pageToken) {
    console.log('pageToken:', pageToken)
    requestOptions.pageToken = pageToken
  }
  let request = gapi.client.youtube.playlistItems.list(requestOptions)
  const response = await new Promise((resolve, reject) => {
    request.execute(resolve); // are you sure this doesn't error?
  });

  response['items'].forEach(function (responceObj) {
    let youtubeTrackTitle = responceObj.snippet.title
    if (youtubeTrackTitle !== 'Deleted video') {
      let youtubeTrackId = responceObj.snippet.resourceId.videoId
      playlistTracks.push({
        youtubePlaylistId: playlistObj.playlistId,
        youtubePlaylistTitle: playlistObj.playlistTitle,
        youtubeTrackId: youtubeTrackId,
        youtubeTrackTitle: youtubeTrackTitle,
      })
    }
  })
  if (response.result['nextPageToken']) {
    const nextPageTracks = await getPlaylistTracks(playlistObj, response.result['nextPageToken']);
    playlistTracks = playlistTracks.concat(nextPageTracks);
  }
  return playlistTracks;
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • It works now, but about 10-15 times slower than before. Is it because of async\await? Can the speed be improved? – TitanFighter Feb 21 '18 at 03:32
  • It's because the loop in `getPlaylistsWithTracks` is sequential now, and everything is properly awaited. For how to make to requests to the tracks concurrently, see the second link in my answer. – Bergi Feb 21 '18 at 03:36
  • 1
    Now I see the correct pattern. Thanks a lot for this, for the help and for the useful links! EDITED: I changed the loop based on a solution from your link and speed is restored. Thanks again :) – TitanFighter Feb 21 '18 at 03:46