3
votes

Comment charger plusieurs demandes de repos

Je dois faire plusieurs requêtes GET pour charger des données à partir d'une page externe.

La réponse de la requête a peut renvoyer un indicateur indiquant qu'il y a plus de données à charger: "nextPage": "/ v1 / catalog / products? page = 2 & pageSize = 10",

Voici le code de ma fonction.

J'ai essayé d'implémenter une boucle do while mais Je ne pouvais pas le faire fonctionner. Je suppose qu'il y a aussi une façon plus intelligente de faire cela - peut-être Switchmap?

Ancienne version

  getProducts(url, dataSoFar = []): Observable<any[]> {
    if (!url) {
      return of (dataSoFar);
    } else {
      url = ZUORA_URL + url;
    }
    return this.http.get<any>(url, { headers }).pipe(
      switchMap(p => this.getProducts( p.nextPage, [...dataSoFar, ...p.data]))
    );
  }

  getData() {
    return this.getProducts('/v1/catalog/products');
  }

Je veux charger les données complètes et les stocker en un seul endroit. Comment puis-je boucler jusqu'à ce qu'il n'y ait plus de page suivante? - le chargement d'une page après l'autre fonctionne maintenant mais j'ai encore du mal à stocker les réponses ...

Version mise à jour

  loadCatalog() {
    return new Promise((resolve, reject) => {
        this.http.get<Catalog[]>(ZUORA_URL + '/v1/catalog/products?page=1&pageSize=10', { headers })
              .pipe(map(data => data))
              .subscribe(data => {
                this.catalog = data;
                resolve(true);
            });
    });
}


2 commentaires

Hors sujet, mais je ne suis vraiment pas sûr qu'il soit recommandé d'envelopper l'observable avec une promesse et de mettre à jour les données comme effet secondaire.


stackoverflow.com/questions/38308866/... Regardez ceci. Cela pourrait résoudre votre problème


3 Réponses :


1
votes

Je ne sais vraiment pas s'il est recommandé d'envelopper l'observable avec une promesse et de mettre à jour les données comme effet secondaire.

async loadAllCatalogs(URL) {
  return new Promise((resolve, reject) => {
    try { 
    let catalogs = [];
    let data = await this.loadCatalog('/v1/catalog/products?page=1&pageSize=10');
    catalogs.push(data); // store catalog as an array since there may be more results based on nextPage key
    while(data.nextPage) {
       data = await this.loadCatalog(data.nextPage);
       catalogs.push(data);
    }
    resolve(catalogs);
    }
    } catch (e) {
      reject(e);
    }
  });
}

Vous pouvez maintenant chaîner les requêtes pour récupérer les données comme ci-dessous

loadCatalog(URL) {
    return new Promise((resolve, reject) => {
        this.http.get<Catalog[]>(ZUORA_URL + URL , { headers })
          .pipe(map(data => data))
          .subscribe(data => {
            resolve(data);
        });
    });
}


3 commentaires

Probablement parce que vous encapsulez une observable avec une promesse au lieu d'utiliser flatMap ou toPromise . Cela n'a aucun sens. Les observables peuvent recevoir plusieurs valeurs. Les promesses ne peuvent pas. Pourquoi même utiliser l'observable si vous voulez le casser?


J'avais déjà ajouté un commentaire sous la question originale avant d'essayer de répondre et j'ai également intitulé ma réponse avec le même titre. Mais là encore, la requête HTTP GET NE renvoie PAS plusieurs valeurs, alors quel est le point? De plus, les promesses sont plus faciles à gérer avec async / await, les observables ne peuvent pas l'être, il y a donc une autre raison à la même chose :)


Et même si cela «casse» les conventions, c'est toujours du code de travail qui résout la douleur de l'auteur. Ça craint quand les gens ne peuvent pas réaliser ça: D



0
votes

Vous pouvez utiliser expand pour appeler votre API de manière récursive et réduisez pour réduire tous les réponses à un seul tableau.

Dans votre Service (MyService):

private products: Product[];

loadProducts() {
  this.myService.fetchData('/v1/catalog/products').subscribe(products => 
    this.products = products as Product[]
  )
}

Dans votre composant:

import { EMPTY } from 'rxjs';
import { expand, reduce, map } from 'rxjs/operators';

baseUrl = ZUORA_URL;

// Let's say you get an object like this from your API
interface ApiResponse {
  nextPage: string,
  data: any[]
}

public fetchData(apiEndpoint: string): Observable<any[]> {
  return this.http.get<ApiResponse>(baseUrl + apiEndpoint, { headers })
    .pipe(
      // recursively call the GET requests until there is no further 'nextPage' url
      expand(apiResponse => {
        if (!apiResponse.nextPage) {
          return EMPTY;
        }
        return this.http.get<ApiResponse>(apiResponse.nextPage, { headers });
      }),
      // map the api response to the data we actually want to return
      map(apiResponse => apiResponse.data),
      // reduce the data of all GET requests to a single array
      reduce((accData, data) => accData.concat(data))
    )
}


2 commentaires

Salut Fridoo, l'application affiche une erreur après la troisième requête: ERREUR TypeError: Impossible de lire la propriété «concat» de undefined avez-vous une idée de la cause du problème?


@Malte essaie de spécifier un tableau vide comme germe dans la fonction reduction : reduction ((accData, data) => accData.concat (data), [])



0
votes

Habituellement, lorsque des éléments sont paginés, vous ne souhaitez les récupérer que lorsqu'un utilisateur les demande activement. Cependant, je vais simplement répondre à votre question en l'état et laisser la conférence de côté.

Je ne sais pas comment vous auriez inclus nextPage lorsque vous attendez un tableau comme votre de niveau supérieur dans la réponse, donc je vais supposer que la réponse est en fait de la forme:

public loadProducts(url: string): Observable<Catalog[]> {
  let nextPage: Subject<string> = new Subject<string>();
  let products: Subject<Catalog[]> = new Subject<Catalog[]>();

  nextPage.subscribe((url: string) => {
    this.fetchProducts(url, products, nextPage);
  }).add(() => products.complete());

  return products;
}

private fetchProducts(url: string, products: Subject<Catalog[]>, nextPage: Subject<string>): void {
  this.http.get<CatalogResponse>(url, { headers })
          .subscribe(response => {
            products.next(response.products);
            if (response.nextPage) {
              nextPage.next(response.nextPage);
            } else {
              nextPage.complete();
            }
          });
}

Pour ce faire, vous pouvez utiliser un Observable .

interface CatalogResponse {
  nextPage: string | undefined;
  products: Catalog[];
}

Vous voudrez vous assurer de mettre en œuvre une opération "annuler" ou "arrêter" au cas où cela se poursuivrait indéfiniment.


0 commentaires