Search code examples
javascriptrecursionpromisees6-promise

There is a function with a promise. Inside this function I call this function again (recursion). How to wait till recursed promise is resolved?


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?


Solution

  • 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;
    }