Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

103
Views
use RXJS para seguir activando llamadas http en angular hasta que se cumpla una condición

Estoy haciendo llamadas a la api de spotify que devuelve un objeto así:

 { next: 'https://api.spotify.com/v1/me/tracks?offset=100&limit=50' items: [...] }

donde los elementos son el resultado de la llamada de red recién realizada y next es la URL que se usará para obtener la siguiente 'página' de resultados. Lo que me gustaría hacer es hacer una llamada inicial sin compensación, y luego seguir haciendo llamadas de red hasta que el siguiente sea nulo, lo que significa que al usuario no le quedan más elementos para obtener. Si no hay más elementos, devuelven nulo para el siguiente.

Esto parece posible, pero parece que no puedo averiguar cómo hacerlo correctamente. Lo que estoy buscando es algo como esto:

 readonly baseSpotifyUrl: string = 'https://api.spotify.com/v1'; constructor(private http: HttpClient) {} public getTracks(url?: string): Observable<any> { return this.http.get(url && url?.length > 0 ? url : `${this.baseSpotifyUrl}/me/tracks?limit=50`); } public getAllTracks(): Observable<any> { const tracks$: Subject<any> = new Subject(); let next: string = ''; this.getTracks(next) .subscribe({ next: (res: any): void => { tracks$.next(res.items); next = res.next; // if res.next !== null, do this again now that we have set next to res.next }, error: (err: any): void => { console.error(err); } }); return tracks$; }

La idea aquí es que mi componente llamará a getAllTracks() y recibirá un asunto y luego los elementos nuevos se enviarán continuamente a través de ese asunto hasta que se hayan recuperado todos los elementos. Parece que no puedo entender cómo hacer una nueva solicitud de red cuando la anterior regresa SOLO SI hay más elementos para obtener (res.next! == null)

EDITAR------------------------------------------------- ----------

Esto hace el trabajo, pero siento que es basura:

 public getAllTracksSegment(itemsSubject: Subject<any>, nextSubject: Subject<string>, url?: string): void { this.http.get(url && url?.length > 0 ? url : `${this.baseSpotifyUrl}/me/tracks?limit=50`).subscribe({ next: (res: any): void => { itemsSubject.next(res.items); nextSubject.next(res.next); } }); } public getAllTracks(): Observable<any> { const tracks$: Subject<any> = new Subject(); const next$: Subject<string> = new Subject(); next$.subscribe({ next: (next: any): void => { if (next !== null) { this.getAllTracksSegment(tracks$, next$, next); } } }); next$.next(''); return tracks$; }
over 4 years ago · Santiago Trujillo
3 answers
Answer question

0

Si entiendo bien el problema, usaría el operador de expand para construir una solución.

Aquí el código que usaría. Los comentarios están en línea

 public getTracks(url?: string): Observable<any> { return this.http.get(url && url?.length > 0 ? url : `${this.baseSpotifyUrl}/me/tracks?limit=50`); } public getAllTracks(): Observable<any[]> { // the first call is with no parameter so that the default url with no offset is used return getTracks().pipe( // expand is used to call recursively getTracks until next is null expand(data => data.next === null ? EMPTY : getTracks(data.next)), // with tap you can see the result returned by each call tap(data => console.log(data)), // if you want you can use the reduce operator to eventually emit the // accumulated array with all items reduce((acc, val) => { acc = [...acc, ...val.items] return acc }, []) ) } // now you can fire the execution of the recursive calls by subscribing // to the observable returned by getAllTracks getAllTracks().subscribe( // allItems is an array containing all the items returned by the various calls allItems => console.log(allItems) )

EXPLICACIONES ADICIONALES tras los comentarios de @skyleguy

El operador del tap se utiliza para implementar efectos secundarios . En otras palabras, recibe todas las notificaciones de la parte superior, hace lo que sea necesario con los datos notificados y luego pasa la misma notificación a la parte inferior. No hay necesidad de devolver nada de la función pasada al operador de tap . El flujo ascendente simplemente se pasa aguas abajo después de que se aplica el efecto secundario . En este ejemplo, el efecto secundario es simplemente la impresión en la consola de los datos pasados con la notificación.

El reduce utilizado dentro de la pipe es el operador de reduce de RxJs y no el método de reduce de Array . El operador reduce RxJs acumula todos los datos notificados desde upstream y emite solo un valor cuando upstream complete s. Entonces, en este ejemplo, cada vez que la llamada a la función remota devuelve algo, este algo ingresa al operador de reduce y contribuye a la lógica de acumulación. Cuando expand devuelve el Observable EMPTY , al final de la recursión, el Observable EMPTY simplemente complete s sin notificar nada, lo que significa que upstream completes y, por lo tanto, reduce puede emitir su primera y única notificación, es decir, la matriz con todos los elementos acumulados, y luego complete

Este stackblitz replica esta lógica con una simulación de la llamada remota.

over 4 years ago · Santiago Trujillo Report

0

Hice esta solución usando RxJs. Espero que te ayude, dame un comentario si funciona.

expand haga búsquedas recursivas y control takeWhile para solicitudes solo cuando Next no sea nulo. Reduzca el grupo de todas sus pistas en una matriz. Al suscribirte tienes todas las pistas disponibles.

 getAllTracks(): Observable<any> { return this.getAllTracksSegment().pipe( expand((res: any) => this.getAllTracksSegment(res.next)), takeWhile((res) => !!res.next), reduce((total, current) => total.concat(current.tracks), []) ); // All tracks available when you subscribe } getAllTracksSegment(url?: string): Observable<any> { return this.http.get(url && url?.length > 0 ? url : `${this.baseSpotifyUrl}/me/tracks?limit=50`); }

PD: si desea emitir resultados parciales, simplemente elimine el operador de reducción y funcionará

over 4 years ago · Santiago Trujillo Report

0

Mientras que otras respuestas mencionan takeWhile() , me gustaría usarlo con una solución más simple/declarativa. Una cosa más, aunque utilicé Observable<any[]> (supongo que items son una matriz), recomendaría encarecidamente agregar una declaración de tipo para el objeto que devuelve la API.

 public getAllTracks$: Observable<any[]>; private nextUrl = new BehaviorSubject<string>('https://api.spotify.com/v1/me/tracks?limit=50'); constructor(private http: HttpClient) { getAllTracks$ = this.nextUrl.pipe( mergeMap(url => this.http.get(url)), takeWhile(res => !!res.next), tap(res => this.nextUrl.next(res.next)), scan((items, res) => [...items, ...res.items], [] as any[]) ); }

Primero definimos un BehaviorSubject que emitirá qué URL llamar para cada solicitud, comenzando con el valor inicial. Luego definimos nuestro observable en el constructor. No se necesitan propiedades o métodos adicionales.

getAllTracks$ suscribe a BehaviorSubject e inmediatamente recibe la primera URL (la cadena que incluimos en su declaración).

Usamos mergeMap() para que llame a todas las solicitudes de API secuenciales. (Si switchMap() , cualquier nueva URL recibida de nuestro BehaviorSubject cancelaría cualquier llamada API en curso y "cambiaría" al nuevo valor de URL). También encuentro esto más sencillo que usar expand() .

Luego, nuestro takeWhile() verifica si nuestra respuesta tiene una propiedad next . Si lo hace, continuamos con los dos últimos operadores. Si no lo hace, emite una señal complete , lo que hace que todos los suscriptores se den de baja.

tap() se usa mejor para actualizaciones de estado externas. Debido a que tenemos nuestra próxima URL, podemos enviarla a nuestro BehaviorSubject, poniendo en cola la próxima llamada API.

Por último, usamos scan() para combinar cómo se presentan estos datos. El operador scan() es como reduce() pero para emisiones observables. En la primera emisión, declara una nueva matriz y agrega cualquier emisión API posterior a su lista.

over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!