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 :
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.
Avant de plonger dans le code, voici quelques fonctionnalités clés de l'API Storyblok à prendre en compte :
Voici comment j'ai implémenté ces concepts à l'aide de la fonction de récupération native en JavaScript.
Considérez cela :
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}`);
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.
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 :
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.
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!