使用处理大型数据集的 API 时,有效管理数据流并解决分页、速率限制和内存使用等挑战至关重要。在本文中,我们将介绍如何使用 JavaScript 的本机 fetch 函数来使用 API。我们将看到重要的主题,例如:
我们将使用 Storyblok Content Delivery API 探索这些技术,并解释如何使用 fetch 在 JavaScript 中处理所有这些因素。让我们深入研究代码。
在深入研究代码之前,请考虑以下 Storyblok API 的一些关键功能:
以下是我如何使用 JavaScript 中的本机 fetch 函数实现这些概念。
考虑一下:
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(请求过多)状态时重试的逻辑,这表示已超出速率限制。
retryAfter 值指定重试之前等待的时间。我在发出后续请求之前使用 setTimeout 暂停,并且重试次数最多限制为 5 次。
2) 初始页面请求和 CV 参数
第一个 API 请求至关重要,因为它会检索总标头(指示故事总数)和 cv 参数(用于缓存)。
您可以使用total header来计算所需的总页数,cv参数确保使用缓存的内容。
3) 处理分页
分页是使用 page 和 per_page 查询字符串参数进行管理的。该代码请求每页 25 个故事(您可以调整此值),总标题有助于计算需要获取的页面数。
该代码一次最多可批量获取 7 个(您可以调整此)并行请求的故事,以提高性能,而不会压垮 API。
4) 使用 Promise.all() 的并发请求:
为了加快该过程,使用 JavaScript 的 Promise.all() 并行获取多个页面。此方法同时发送多个请求并等待所有请求完成。
每批并行请求完成后,将处理结果以生成故事。这样可以避免一次将所有数据加载到内存中,从而减少内存消耗。
5) 异步迭代的内存管理(用于await...of):
我们没有将所有数据收集到数组中,而是使用 JavaScript 生成器(function* 和 wait...of)来处理获取的每个故事。这可以防止处理大型数据集时出现内存过载。
通过一一生成故事,代码保持高效并避免内存泄漏。
6) 速率限制处理:
如果 API 以 429 状态代码(速率受限)响应,则脚本使用 retryAfter 值。然后,它会暂停指定的时间,然后重试请求。这可确保符合 API 速率限制并避免过快发送过多请求。
在本文中,我们介绍了使用本机 fetch 函数在 JavaScript 中使用 API 时的关键注意事项。我尝试处理:
通过应用这些技术,您可以以可扩展、高效且内存安全的方式处理 API 消耗。
请随时发表您的评论/反馈。
以上是JavaScript 中海量数据的高效 API 消耗的详细内容。更多信息请关注PHP中文网其他相关文章!