Search code examples
expresspromiseobservablees6-promisenestjs

How to wait until all async calls are finished


I've got NestJS application which interact with YoutubeAPI and load videos from it. One particular method is important and it's loadVideos from below. Method it self has multiple asyncs inside and I need to work with videoIdMap property once everything is finished

private loadVideos(
    playListId: string,
    channel: Channel,
    nextPageToken: string,
    stopLoadingOnVideoId: string,
  ) {
    const baseUrl = YoutubeService.VIDEO_URL_SNIPPET_BY_ID + playListId;
    const response = this.httpService
      .get(nextPageToken ? baseUrl + '&pageToken=' + nextPageToken : baseUrl)
      .pipe(map((response) => response.data));
    response.subscribe((data) => {
      data.items.forEach((item) => {
        if (stopLoadingOnVideoId && item.snippet.resourceId.videoId === stopLoadingOnVideoId) {
          return;
        }        
        this.prepareVideoEntity(item.snippet, channel).then((partialVideo) =>              
          this.videoService.create(partialVideo).then((video) => {     
            this.videoIdMap[video.youtubeId] = video.id;
          }),
        );
      });      
      if (data.nextPageToken) {        
        this.loadVideos(
          playListId,
          channel,
          data.nextPageToken,
          stopLoadingOnVideoId,
        );
      }
    });
  }

Ideal solution for me would be to make loadVideos async somehow so I can later do:

public methodWhichCallLoadVideos(): void {
  await loadVideos(playListId, channel, null, stopLoadingOnVideoId)
  // My code which have to be executed right after videos are loaded
}

Every solution I tried out end up with this.videoIdMap to be empty object or with compilation issue so any idea is more than welcome.


Solution

  • You could switch to promises instead of Observables, thus turning the method into an async one that recurs as long as data has a nextPageToken:

    private async loadVideos(
            playListId: string,
            channel: Channel,
            nextPageToken: string,
            stopLoadingOnVideoId: string,
        ) {
            const baseUrl = YoutubeService.VIDEO_URL_SNIPPET_BY_ID + playListId;
            const response = await this.httpService
                .get(nextPageToken ? url + '&pageToken=' + nextPageToken : url).toPromise();
            const { data } = response;
            for (const item of data.items) {
                if (stopLoadingOnVideoId && item.snippet.resourceId.videoId === stopLoadingOnVideoId) {
                    continue;
                }
                const partialVideo = await this.prepareVideoEntity(item.snippet, channel);
                const video = await this.videoService.create(partialVideo)
                this.videoIdMap[video.youtubeId] = video.id;
            }
            if (data.nextPageToken) {
                await this.loadVideos(
                    playListId,
                    channel,
                    data.nextPageToken,
                    stopLoadingOnVideoId,
                );
            }
        }
    

    In your caller you can then simply await loadVideos(...):

    private async initVideoIdMap(...) {
      await this.loadVideos(...);
      // this.videoIdMap should be correctly populated at this point
    }