NestJS+RxJS递归调用YouTube API获取视频失败问题
Fixing NestJS YouTube API Pagination: Fetching All Paginated Videos
Let's break down why your current code isn't pulling in subsequent pages of videos, then fix it with a clean, working implementation.
What's Wrong With the Original Code?
Your core issue is a misuse of RxJS operators:
- The
tapoperator only handles side effects (like logging) — it doesn't modify or replace the Observable stream. AnymergeMaporempty()you return insidetapis completely ignored, so your subsequent page requests never actually run. - You had a parameter name bug: you referenced
video['nextPageToken']instead ofvideoResponse['nextPageToken']in yourmergeMap. toPromise()is deprecated in RxJS 7+; we should use modern alternatives likefirstValueFrom.
Fixed Implementation (RxJS-Based)
Here's a corrected version that properly handles recursive pagination:
import { Injectable } from '@nestjs/common'; import { HttpService } from '@nestjs/axios'; import { firstValueFrom, empty, mergeMap } from 'rxjs'; import { map, tap } from 'rxjs/operators'; @Injectable() export class YoutubeService { private videos: any[] = []; constructor(private readonly httpService: HttpService) {} private getPlaylistURL(playlistId: string, pageToken?: string): string { // Replace with your actual YouTube API URL construction logic const baseUrl = 'https://www.googleapis.com/youtube/v3/playlistItems'; const apiKey = 'YOUR_YOUTUBE_API_KEY'; // Add your API key here let url = `${baseUrl}?part=snippet&maxResults=50&playlistId=${playlistId}&key=${apiKey}`; if (pageToken) url += `&pageToken=${pageToken}`; return url; } async getPlaylistVideos(playlistId: string): Promise<any[]> { this.videos = []; // Recursive function to fetch a single page and trigger next page if needed const fetchPage = (pageToken?: string) => { return this.httpService.get(this.getPlaylistURL(playlistId, pageToken)).pipe( map(response => response.data), tap(videoResponse => { console.log(`\tFetched page with token: ${pageToken ?? 'none'}`); this.videos.push(...videoResponse.items); }), mergeMap(videoResponse => { if (videoResponse.nextPageToken) { console.log(`\tFetching next page with token: ${videoResponse.nextPageToken}`); return fetchPage(videoResponse.nextPageToken); } else { console.log('\tNo more pages to fetch'); return empty(); } }) ); }; try { await firstValueFrom(fetchPage()); return this.videos; } catch (err) { console.error('Failed to fetch playlist videos:', err); throw err; } } }
Key Improvements
- Moved pagination logic to
mergeMap: This operator replaces the current Observable with a new one (the next page request) when anextPageTokenexists, enabling proper recursive fetching. - Used
firstValueFrom: Replaces the deprecatedtoPromise()with RxJS's recommended approach for converting Observables to Promises. - Cleaner recursive structure: The
fetchPagefunction encapsulates single-page logic, making the code easier to follow and debug. - Fixed parameter name bug: Properly references
videoResponse.nextPageTokeninstead of the incorrectvideovariable.
Alternative: Async/Await Only (No RxJS Operators)
If you prefer a simpler approach without RxJS operators (more aligned with NestJS's async/await style), here's an even cleaner version:
async getPlaylistVideos(playlistId: string): Promise<any[]> { const videos: any[] = []; const fetchPage = async (pageToken?: string) => { const response = await firstValueFrom( this.httpService.get(this.getPlaylistURL(playlistId, pageToken)) ); const videoResponse = response.data; videos.push(...videoResponse.items); console.log(`\tFetched ${videoResponse.items.length} videos | Total: ${videos.length}`); if (videoResponse.nextPageToken) { await fetchPage(videoResponse.nextPageToken); } }; try { await fetchPage(); return videos; } catch (err) { console.error('Failed to fetch playlist videos:', err); throw err; } }
This version avoids RxJS operators entirely, using async/await recursion to handle pagination — it's often easier to reason about for simple recursive workflows.
内容的提问来源于stack exchange,提问作者Sankalp
相关产品推荐
相关产品推荐

