Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

364
Vistas
¿Cómo completar un suscriptor cuando se realiza un bucle for?

Si realiza una solicitud GET a https://api.github.com/users/benawad/repos , obtendrá todos los repositorios que tiene el usuario benawad . Al menos eso es lo que se espera que devuelva el punto final de la API. El problema es que GitHub limita el número de repositorios a 30.

ingrese la descripción de la imagen aquí

Al día de hoy (14/08/2021), el usuario benawad tiene 246 repositorios .

Para superar el problema mencionado anteriormente, debe realizar una solicitud GET pero con algunos parámetros adicionales... GitHub ha implementado la paginación en su API. Por lo tanto, en la URL debe especificar la página que desea recuperar y la cantidad de repositorios por página .

Nuestra nueva solicitud GET debería verse así:

https://api.github.com/users/benawad/repos?page=1&per_page=1000

El problema con esto es que GitHub ha limitado la cantidad de repositorios por página a 100. Entonces nuestra solicitud GET devuelve 100 repositorios y no los 246 que tiene actualmente el usuario benawad .

ingrese la descripción de la imagen aquí

En mi servicio Angular, implementé el siguiente código para recuperar todos los repositorios de todas las páginas.

 public getUserRepos(user: string): Observable<RepositoryI[]> { return new Observable((subscriber: Subscriber<RepositoryI[]>) => { this.getUserData(user).subscribe((data: UserI) => { //public_repos is the amt of repos the user has const pages: number = Math.ceil(data.public_repos / 100); for (let i = 1; i <= pages; i++) { this.http .get( `https://api.github.com/users/${user}/repos?page=${i}&per_page=100` ) .subscribe((data: RepositoryI[]) => { subscriber.next(data); }); } }); }); }

Y en mi componente me suscribo con lo siguiente:

 this.userService.getUserRepos(id).subscribe((repos)=>{ this.repositories.push(...repos); })

El problema con este enfoque es que no tengo control de cuándo el Observable ha dejado de emitir valores. En mi componente, me gustaría activar una función cuando el Observable esté completo .

He intentado lo siguiente:

 public getUserRepos(user: string): Observable<RepositoryI[]> { return new Observable((subscriber: Subscriber<RepositoryI[]>) => { this.getUserData(user).subscribe((data: UserI) => { const pages: number = Math.ceil(data.public_repos / 100); for (let i = 1; i <= pages; i++) { this.http .get( `https://api.github.com/users/${user}/repos?page=${i}&per_page=100` ) .subscribe((data: RepositoryI[]) => { subscriber.next(data); // If the for loop is complete -> complete the subscriber if(pages == i) subscriber.complete(); }); } }); }); }

Y en mi componente hago lo siguiente:

 this.userService.getUserRepos(id).subscribe( (repos) => { this.repositories.push(...repos); }, (err) => { console.log(err); }, () => { // When the observable is complete: console.log(this.repositories); // This only logs 46 repositores, it should log 246 // I want to trigger some function here } );

El console.log() solo registra 46 repositorios. ¿Por qué está pasando esto? Tal vez estoy completando el suscriptor antes de que pueda obtener las 3 páginas; pero estoy llamando al .complete() dentro de la suscripción. ¿Qué estoy haciendo mal? Gracias por adelantado.

over 4 years ago · Santiago Trujillo
5 Respuestas
Responde la pregunta

0

 let subs = interval(1000).subscribe(() => { this.http .get( `https://api.github.com/users/${user}/repos?page=${i}&per_page=100` ) .subscribe((data: RepositoryI[]) => { subscriber.next(data); if(!data) { // to check if data is null subs = null; // if null, cancel the interval subscription return; // and return (not continue the subscription) } }); }) }

De esta forma almacenas la suscripción y después de comprobar si hay datos, te das de baja.

Por favor, avíseme si esto funciona.

over 4 years ago · Santiago Trujillo Denunciar

0

Javascript ejecuta las cosas de forma asíncrona.

Su 'bucle For' ejecutará api en paralelo e incrementará el conteo al mismo tiempo.

A medida que realiza 3 llamadas API paralelas, es posible que regresen con una duración diferente.

Pero antes de eso, su valor 'i' llegará a 3.

Entonces, en su caso, la posibilidad es que su tercera llamada API que tiene menos registros (46 registros) regrese antes de las primeras 2 llamadas API y vaya primero a la lógica de suscripción y solo vea 46 registros.

 subscriber.next(data); // 3rd api comes here if(pages == i) subscriber.complete(); // now your pages and i values are same and it gets completed. });

SOLUCIÓN

 import {defer } from 'rxjs'; defer(async () => { for (let i = 1; i <= pages; i++) { await this.http .get( `https://api.github.com/users/${user}/repos?page=${i}&per_page=100` ) .toPromise().then((data: RepositoryI[]) => { subscriber.next(data); }); } }.subscribe(x => { subscriber.complete(); });
over 4 years ago · Santiago Trujillo Denunciar

0

Resumen

Describiré dos formas de lograr esto, una es imperativa (usando un bucle) y la otra reactiva (usando rxjs)

Imperativo

Puede hacer un bucle simple para obtener todas las páginas (este devuelve una promesa, pero si es necesario, puede convertirlo en un observable usando from ):

 public getUserRepos(user: string): Promise<Repository[]> { let pageNumber = 0; let results = []; const getPage = pageNumber => this.http .get<Repository[]>(`https://api.github.com/users/${user}/repos?page=${pageNumber}&per_page=100`) .toPromise(); do { const pageResults = await getPage(pageNumber++); results = results.concat(pageResults); } while(pageResults.length !== 0); return results; }

Reactivo

Puede usar el operador de expand de rxjs para consultar una página, luego la página siguiente, etc.

El uso requiere que asigne el resultado de http a un objeto que también incluya el número de página, de esa manera expand puede saber qué página obtener a continuación.

Ejemplo en tu caso:

 public getUserRepos(user: string): Observable<Repository[]> { const getPage = pageNumber => this.http .get<Repository[]>(`https://api.github.com/users/${user}/repos?page=${pageNumber}&per_page=100`) .pipe(map(z => ({ results: z, page: pageNumber }))); // Add page number to results return getPage(0).pipe( expand(pr => getPage(pr.page + 1)), takeWhile(pr => pr.results.length !== 0), map(pr => pr.results), scan((acc, v) => [...acc, ...v]) ); }

El resultado sería una consulta para la página 0, luego, cuando se resuelva, una consulta para la página 1, etc. cuando una página resulte en 0 entradas, el observable se completará.

Usé el scan para proporcionar resultados parciales a medida que se recuperan las páginas, de esa manera puede ver que sucede algo, en lugar de esperar a que se resuelvan todas las páginas antes de mostrar algo.

Ejemplo de StackBlitz

Nota: Habrá una consulta adicional para una página después de la última que se cancelará, es inofensivo, pero si desea evitar eso, cambie su llamada de expand a esto:

 expand(pr => (pr.results.length !== 0 ? getPage(pr.page + 1) : EMPTY))
over 4 years ago · Santiago Trujillo Denunciar

0

Puede lograrlo con una suscripción usando operadores y funciones RxJS, como las siguientes:

 public getUserRepos(user: string): Observable<RepositoryI[]> { // will return one observable that contains all the repos after concating them to each other: return this.getUserData(user).pipe( switchMap((data: UserI) => { //public_repos is the amt of repos the user has const pages: number = Math.ceil(data.public_repos / 100); // forkJoin will emit the result as (RepositoryI[][]) once all the sub-observables are completed: return forkJoin( Array.from(new Array(pages)).map((_, page) => this.http.get<RepositoryI[]>( `https://api.github.com/users/${user}/repos?page=${page + 1}&per_page=100` ) ) ).pipe( // will reduce the RepositoryI[][] to be RepositoryI[] after concating them to each other: map((res) => res.reduce((acc, value) => { return acc.concat(value); }, []) ) ); }) ); }

Luego, en su componente, puede suscribirse al observable que devolverá todos los repositorios después de obtenerlos todos:

 this.userService.getUserRepos(id).subscribe((repos) => { this.repositories = repos; // You can trigger the function that you need here... });
over 4 years ago · Santiago Trujillo Denunciar

0

Intentando dar una respuesta más directa. Si no le gusta el anidamiento, siempre puede declarar un observable interno como un método separado.

 public getUserRepos = (user:string) => this.getUserData(user) .pipe( map(({public_repos})=>Math.ceil(public_repos / 100)), switchMap(max=>interval(1) .pipe( takeWhile(i=>++i<=max) mergeMap(i=>this.http.get<RepositoryI[]>(`https://api.github.com/users/${user}/repos?page=${++i}&per_page=100`)) )), scan((acc, repo)=>[...acc, ...repo], [] as RepositoryI[]) );

Aquí está la estrategia que usé:

  1. Obtenga el límite de páginas (usando la desestructuración de objetos) de getUserData() como lo que ya tiene.
  2. Use el interval() para comenzar un conteo observable (comienza con 0 , por lo tanto, el uso de ++ más adelante).
  3. Use takeWhile() para verificar si el contador ha excedido el límite de páginas; de lo contrario, el observable se completa aquí.
  4. Usa mergeMap() para poner en cola una serie de llamadas API para cada número emitido desde interval() .
  5. Reduzca todas las respuestas a una sola respuesta de matriz con scan() .

Actualizar Si prueba este método, deberá actualizar la lógica de su componente.

Debido a que scan() reduce todas las solicitudes de API a un solo observable, puede asignar estos valores a su matriz de estado en lugar de enviarlos.

Este observable se emitirá inmediatamente después de la primera solicitud de API. A medida que se complete cada solicitud de API adicional, el observable emitirá una nueva matriz con los elementos agregados. Esto se hace para que su componente no tenga que esperar hasta que se completen todas las solicitudes de API antes de que pueda mostrar algunos datos.

over 4 years ago · Santiago Trujillo Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda