Maison  >  Article  >  interface Web  >  Consommation efficace d'API pour des données volumineuses en JavaScript

Consommation efficace d'API pour des données volumineuses en JavaScript

Susan Sarandon
Susan Sarandonoriginal
2024-10-20 20:42:02412parcourir

Efficient API consumption for huge data in JavaScript

Lorsque vous travaillez avec des API qui gèrent de grands ensembles de données, il est crucial de gérer efficacement le flux de données et de relever des défis tels que la pagination, les limites de débit et l'utilisation de la mémoire. Dans cet article, nous expliquerons comment consommer des API à l'aide de la fonction de récupération native de JavaScript. Nous verrons des sujets importants comme :

  • Gestion d'énormes quantités de données : récupération progressive de grands ensembles de données pour éviter de surcharger votre système.
  • Pagination : la plupart des API, y compris l'API Storyblok Content Delivery, renvoient des données dans des pages. Nous explorerons comment gérer la pagination pour une récupération efficace des données.
  • Limites de débit : les API imposent souvent des limites de débit pour éviter les abus. Nous verrons comment détecter et gérer ces limites.
  • Mécanisme Retry-After : si l'API répond avec un code d'état 429 (Trop de requêtes), nous implémenterons le mécanisme "Retry-After", qui indique combien de temps attendre avant de réessayer pour garantir la fluidité des données. récupérer.
  • Demandes simultanées : récupérer plusieurs pages en parallèle peut accélérer le processus. Nous utiliserons Promise.all() de JavaScript pour envoyer des requêtes simultanées et améliorer les performances.
  • Éviter les fuites de mémoire : la gestion de grands ensembles de données nécessite une gestion minutieuse de la mémoire. Nous traiterons les données par morceaux et garantirons des opérations économes en mémoire, grâce aux générateurs.

Nous explorerons ces techniques à l'aide de l'API Storyblok Content Delivery et expliquerons comment gérer tous ces facteurs en JavaScript à l'aide de fetch. Plongeons dans le code.

Points à garder à l’esprit lors de l’utilisation de l’API Storyblok Content Delivery

Avant de plonger dans le code, voici quelques fonctionnalités clés de l'API Storyblok à prendre en compte :

  • Paramètre CV : le paramètre cv (Content Version) récupère le contenu mis en cache. La valeur cv est renvoyée dans la première requête et doit être transmise dans les requêtes suivantes pour garantir que la même version en cache du contenu est récupérée.
  • Pagination avec page et par page : utilisation des paramètres page et per_page pour contrôler le nombre d'éléments renvoyés dans chaque requête et parcourir les pages de résultats.
  • En-tête total : L'en-tête total de la première réponse indique le nombre total d'éléments disponibles. Ceci est essentiel pour calculer le nombre de pages de données à récupérer.
  • Gestion 429 (limite de débit) : Storyblok applique les limites de débit ; lorsque vous les frappez, l'API renvoie un statut 429. Utilisez l'en-tête Retry-After (ou une valeur par défaut) pour savoir combien de temps attendre avant de réessayer la demande.

Exemple de code JavaScript utilisant fetch() pour gérer de grands ensembles de données

Voici comment j'ai implémenté ces concepts à l'aide de la fonction de récupération native en JavaScript.
Considérez cela :

  • Cet extrait crée un nouveau fichier nommé stories.json à titre d'exemple. Si le fichier existe déjà, il sera écrasé. Donc, si vous avez déjà un fichier portant ce nom dans le répertoire de travail, modifiez le nom dans l'extrait de code.
  • Comme les requêtes sont exécutées en parallèle, l'ordre des histoires n'est pas garanti. Par exemple, si la réponse de la troisième page est plus rapide que la réponse de la deuxième requête, les générateurs délivreront les histoires de la troisième page avant les histoires de la deuxième page.
  • J'ai testé l'extrait avec Bun :)
import { writeFile, appendFile } from "fs/promises";

// Read access token from Environment
const STORYBLOK_ACCESS_TOKEN = process.env.STORYBLOK_ACCESS_TOKEN;
// Read access token from Environment
const STORYBLOK_VERSION = process.env.STORYBLOK_VERSION;

/**
 * Fetch a single page of data from the API,
 * with retry logic for rate limits (HTTP 429).
 */
async function fetchPage(url, page, perPage, cv) {
  let retryCount = 0;
  // Max retry attempts
  const maxRetries = 5;
  while (retryCount <= maxRetries) {
    try {
      const response = await fetch(
        `${url}&page=${page}&per_page=${perPage}&cv=${cv}`,
      );
      // Handle 429 Too Many Requests (Rate Limit)
      if (response.status === 429) {
        // Some APIs provides you the Retry-After in the header
        // Retry After indicates how long to wait before retrying.
        // Storyblok uses a fixed window counter (1 second window)
        const retryAfter = response.headers.get("Retry-After") || 1;
        console.log(response.headers,
          `Rate limited on page ${page}. Retrying after ${retryAfter} seconds...`,
        );
        retryCount++;
        // In the case of rate limit, waiting 1 second is enough.
        // If not we will wait 2 second at the second tentative,
        // in order to progressively slow down the retry requests
        // setTimeout accept millisecond , so we have to use 1000 as multiplier
        await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000 * retryCount));
        continue;
      }

      if (!response.ok) {
        throw new Error(
          `Failed to fetch page ${page}: HTTP ${response.status}`,
        );
      }
      const data = await response.json();
      // Return the stories data of the current page
      return data.stories || [];
    } catch (error) {
      console.error(`Error fetching page ${page}: ${error.message}`);
      return []; // Return an empty array if the request fails to not break the flow
    }
  }
  console.error(`Failed to fetch page ${page} after ${maxRetries} attempts`);
  return []; // If we hit the max retry limit, return an empty array
}

/**
 * Fetch all data in parallel, processing pages in batches
 * as a generators (the reason why we use the `*`)
 */
async function* fetchAllDataInParallel(
  url,
  perPage = 25,
  numOfParallelRequests = 5,
) {

  let currentPage = 1;
  let totalPages = null;

  // Fetch the first page to get:
  // - the total entries (the `total` HTTP header)
  // - the CV for caching (the `cv` atribute in the JSON response payload)
  const firstResponse = await fetch(
    `${url}&page=${currentPage}&per_page=${perPage}`,
  );
  if (!firstResponse.ok) {
    console.log(`${url}&page=${currentPage}&per_page=${perPage}`);
    console.log(firstResponse);
    throw new Error(`Failed to fetch data: HTTP ${firstResponse.status}`);
  }
  console.timeLog("API", "After first response");

  const firstData = await firstResponse.json();
  const total = parseInt(firstResponse.headers.get("total"), 10) || 0;
  totalPages = Math.ceil(total / perPage);

  // Yield the stories from the first page
  for (const story of firstData.stories) {
    yield story;
  }

  const cv = firstData.cv;

  console.log(`Total pages: ${totalPages}`);
  console.log(`CV parameter for caching: ${cv}`);

  currentPage++; // Start from the second page now

  while (currentPage <= totalPages) {
    // Get the list of pages to fetch in the current batch
    const pagesToFetch = [];
    for (
      let i = 0;
      i < numOfParallelRequests && currentPage <= totalPages;
      i++
    ) {
      pagesToFetch.push(currentPage);
      currentPage++;
    }

    // Fetch the pages in parallel
    const batchRequests = pagesToFetch.map((page) =>
      fetchPage(url, page, perPage, firstData, cv),
    );

    // Wait for all requests in the batch to complete
    const batchResults = await Promise.all(batchRequests);
    console.timeLog("API", `Got ${batchResults.length} response`);
    // Yield the stories from each batch of requests
    for (let result of batchResults) {
      for (const story of result) {
        yield story;
      }
    }
    console.log(`Fetched pages: ${pagesToFetch.join(", ")}`);
  }
}

console.time("API");
const apiUrl = `https://api.storyblok.com/v2/cdn/stories?token=${STORYBLOK_ACCESS_TOKEN}&version=${STORYBLOK_VERSION}`;
//const apiUrl = `http://localhost:3000?token=${STORYBLOK_ACCESS_TOKEN}&version=${STORYBLOK_VERSION}`;

const stories = fetchAllDataInParallel(apiUrl, 25,7);

// Create an empty file (or overwrite if it exists) before appending
await writeFile('stories.json', '[', 'utf8'); // Start the JSON array
let i = 0;
for await (const story of stories) {
  i++;
  console.log(story.name);
  // If it's not the first story, add a comma to separate JSON objects
  if (i > 1) {
    await appendFile('stories.json', ',', 'utf8');
  }
  // Append the current story to the file
  await appendFile('stories.json', JSON.stringify(story, null, 2), 'utf8');
}
// Close the JSON array in the file
await appendFile('stories.json', ']', 'utf8'); // End the JSON array
console.log(`Total Stories: ${i}`);

Étapes clés expliquées

Voici un aperçu des étapes cruciales du code qui garantissent une consommation efficace et fiable de l'API à l'aide de l'API Storyblok Content Delivery :

1) Récupération de pages avec mécanisme de tentatives (fetchPage)

Cette fonction gère la récupération d'une seule page de données à partir de l'API. Il inclut une logique pour réessayer lorsque l'API répond avec un statut 429 (trop de requêtes), ce qui signale que la limite de débit a été dépassée.
La valeur retryAfter spécifie combien de temps attendre avant de réessayer. J'utilise setTimeout pour faire une pause avant de faire la demande suivante, et les tentatives sont limitées à un maximum de 5 tentatives.

2) Demande de page initiale et le paramètre CV

La première requête API est cruciale car elle récupère l'en-tête total (qui indique le nombre total d'histoires) et le paramètre cv (utilisé pour la mise en cache).
Vous pouvez utiliser l'en-tête total pour calculer le nombre total de pages requises, et le paramètre cv garantit que le contenu mis en cache est utilisé.

3) Gestion de la pagination

La pagination est gérée à l'aide des paramètres de chaîne de requête page et per_page. Le code demande 25 histoires par page (vous pouvez ajuster cela) et l'en-tête total permet de calculer le nombre de pages à récupérer.
Le code récupère les histoires par lots allant jusqu'à 7 (vous pouvez ajuster cela) requêtes parallèles à la fois pour améliorer les performances sans surcharger l'API.

4) Demandes simultanées avec Promise.all() :

Pour accélérer le processus, plusieurs pages sont récupérées en parallèle à l'aide de Promise.all() de JavaScript. Cette méthode envoie plusieurs requêtes simultanément et attend qu'elles soient toutes terminées.
Une fois chaque lot de requêtes parallèles terminé, les résultats sont traités pour produire les histoires. Cela évite de charger toutes les données en mémoire en même temps, réduisant ainsi la consommation de mémoire.

5) Gestion de la mémoire avec itération asynchrone (pour wait...of) :

Au lieu de collecter toutes les données dans un tableau, nous utilisons des générateurs JavaScript (fonction* et pour wait...of) pour traiter chaque histoire au fur et à mesure qu'elle est récupérée. Cela évite la surcharge de mémoire lors de la gestion de grands ensembles de données.
En cédant les stories une à une, le code reste efficace et évite les fuites mémoire.

6) Gestion des limites de débit :

Si l'API répond avec un code d'état 429 (à débit limité), le script utilise la valeur retryAfter. Il fait ensuite une pause pendant la durée spécifiée avant de réessayer la demande. Cela garantit le respect des limites de débit de l'API et évite d'envoyer trop de requêtes trop rapidement.

Conclusion

Dans cet article, nous avons abordé les principales considérations lors de la consommation d'API en JavaScript à l'aide de la fonction de récupération native. J'essaie de gérer :

  • Grands ensembles de données : récupération de grands ensembles de données à l'aide de la pagination.
  • Pagination : gestion de la pagination avec les paramètres page et per_page.
  • Limites de débit et mécanisme de nouvelle tentative : gestion des limites de débit et nouvelle tentative des demandes après le délai approprié.
  • Requêtes simultanées : récupération de pages en parallèle à l'aide de Promise.all() de JavaScript pour accélérer la récupération des données.
  • Gestion de la mémoire : utilisation de générateurs JavaScript (fonction* et pour wait...of) pour traiter les données sans consommer de mémoire excessive.

En appliquant ces techniques, vous pouvez gérer la consommation des API de manière évolutive, efficace et respectueuse de la mémoire.

N'hésitez pas à laisser vos commentaires/commentaires.

Références

  • Générateurs JavaScript
  • Bun le runtime JavaScript
  • L'API de diffusion de contenu Storyblok

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn