ホームページ > 記事 > ウェブフロントエンド > JavaScript での膨大なデータの API の効率的な使用
大規模なデータセットを処理する API を操作する場合、データ フローを効率的に管理し、ページネーション、レート制限、メモリ使用量などの課題に対処することが重要です。この記事では、JavaScript のネイティブ フェッチ関数を使用して API を使用する方法について説明します。次のような重要なトピックが表示されます:
Storyblok Content Delivery API を使用してこれらのテクニックを検討し、フェッチを使用して JavaScript でこれらすべての要素を処理する方法を説明します。コードを詳しく見てみましょう。
コードの説明に入る前に、考慮すべき Storyblok API の重要な機能をいくつか説明します。
JavaScript のネイティブフェッチ関数を使用してこれらの概念を実装する方法を次に示します。
次のことを考慮してください:
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}`);
Storyblok Content Delivery API を使用して効率的かつ信頼性の高い API を使用するための、コード内の重要な手順の内訳を次に示します。
1) 再試行メカニズムによるページの取得 (fetchPage)
この関数は、API からの単一ページのデータの取得を処理します。これには、API がレート制限を超えたことを示す 429 (Too Many Requests) ステータスで応答したときに再試行するロジックが含まれています。
retryAfter 値は、再試行するまでの待機時間を指定します。 setTimeout を使用して次のリクエストを行う前に一時停止し、再試行は最大 5 回に制限されています。
2) 初期ページリクエストと CV パラメータ
最初の API リクエストは、合計ヘッダー (ストーリーの合計数を示す) と cv パラメーター (キャッシュに使用される) を取得するため、非常に重要です。
合計ヘッダーを使用して必要なページの合計数を計算でき、cv パラメーターによりキャッシュされたコンテンツが確実に使用されます。
3) ページネーションの処理
ページネーションは、page および per_page クエリ文字列パラメーターを使用して管理されます。このコードは 1 ページあたり 25 のストーリーを要求し (これは調整できます)、合計ヘッダーは取得する必要があるページ数を計算するのに役立ちます。
このコードは、一度に最大 7 つの並列リクエスト (これは調整可能) のバッチでストーリーをフェッチし、API に負担をかけずにパフォーマンスを向上させます。
4) Promise.all() による同時リクエスト:
プロセスを高速化するために、JavaScript の Promise.all() を使用して複数のページが並行して取得されます。このメソッドは、複数のリクエストを同時に送信し、すべてのリクエストが完了するまで待機します。
並列リクエストの各バッチが完了すると、結果が処理されてストーリーが生成されます。これにより、すべてのデータを一度にメモリにロードすることがなくなり、メモリの消費量が削減されます。
5) 非同期反復によるメモリ管理 (await...of 用):
すべてのデータを配列に収集する代わりに、JavaScript ジェネレーター (関数* および for await...of) を使用して、取得された各ストーリーを処理します。これにより、大規模なデータセットを処理する際のメモリの過負荷が防止されます。
ストーリーを 1 つずつ生成することで、コードの効率性が維持され、メモリ リークが回避されます。
6) レート制限の処理:
API が 429 ステータス コード (レート制限) で応答した場合、スクリプトは retryAfter 値を使用します。その後、指定された時間だけ一時停止してから、リクエストを再試行します。これにより、API レート制限への準拠が保証され、あまりにも多くのリクエストが急速に送信されることが回避されます。
この記事では、ネイティブのフェッチ関数を使用して JavaScript で API を使用する際の重要な考慮事項について説明しました。私は次のことを処理しようとします:
これらの手法を適用すると、スケーラブルで効率的かつメモリ安全な方法で API の使用を処理できます。
お気軽にコメント/フィードバックをお寄せください。
以上がJavaScript での膨大なデータの API の効率的な使用の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。