JavaScript 中海量数据的高效 API 消耗
使用处理大型数据集的 API 时,有效管理数据流并解决分页、速率限制和内存使用等挑战至关重要。在本文中,我们将介绍如何使用 JavaScript 的本机 fetch 函数来使用 API。我们将看到重要的主题,例如:
- 处理大量数据:增量检索大型数据集以避免系统不堪重负。
- 分页:大多数 API(包括 Storyblok Content Delivery API)以页面形式返回数据。我们将探索如何管理分页以实现高效的数据检索。
- 速率限制:API 通常会施加速率限制以防止滥用。我们将了解如何检测和处理这些限制。
- Retry-After机制:如果API响应429状态码(请求过多),我们将实现“Retry-After”机制,指示重试之前需要等待多长时间,以确保数据流畅正在获取。
- 并发请求:并行获取多个页面可以加快进程。我们将使用 JavaScript 的 Promise.all() 发送并发请求并提高性能。
- 避免内存泄漏:处理大型数据集需要仔细的内存管理。借助生成器。 ,我们将分块处理数据并确保内存高效操作
我们将使用 Storyblok Content Delivery API 探索这些技术,并解释如何使用 fetch 在 JavaScript 中处理所有这些因素。让我们深入研究代码。
使用 Storyblok Content Delivery API 时要记住的事项
在深入研究代码之前,请考虑以下 Storyblok API 的一些关键功能:
- CV 参数:cv(内容版本)参数检索缓存的内容。 cv 值在第一个请求中返回,并应在后续请求中传递,以确保获取内容的相同缓存版本。
- 分页和每页分页:使用 page 和 per_page 参数来控制每个请求中返回的项目数并迭代结果页面。
- 总标题:第一个响应的总标题指示可用项目的总数。这对于计算需要获取多少数据页至关重要。
- 处理 429(速率限制):Storyblok 强制执行速率限制;当您点击它们时,API 会返回 429 状态。使用 Retry-After 标头(或默认值)了解重试请求之前需要等待多长时间。
使用 fetch() 处理大型数据集的 JavaScript 示例代码
以下是我如何使用 JavaScript 中的本机 fetch 函数实现这些概念。
考虑一下:
- 此代码片段创建一个名为 Stories.json 的新文件作为示例。如果该文件已经存在,它将被覆盖。因此,如果工作目录中已有具有该名称的文件,请更改代码片段中的名称。
- 由于请求是并行执行的,因此无法保证故事的顺序。例如,如果第三页的响应比第二个请求的响应快,则生成器将在第二页的故事之前传送第三页的故事。
- 我用 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}`);
关键步骤说明
以下是代码中关键步骤的细分,可确保使用 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 时的关键注意事项。我尝试处理:
- 大型数据集:使用分页获取大型数据集。
- 分页:使用 page 和 per_page 参数管理分页。
- 速率限制和重试机制:处理速率限制并在适当的延迟后重试请求。
- 并发请求:使用 JavaScript 的 Promise.all() 并行获取页面,以加快数据检索速度。
- 内存管理:使用JavaScript生成器(function*和await...of)来处理数据而不消耗过多的内存。
通过应用这些技术,您可以以可扩展、高效且内存安全的方式处理 API 消耗。
请随时发表您的评论/反馈。
参考
- JavaScript 生成器
- 构建 JavaScript 运行时
- Storyblok 内容交付 API
以上是JavaScript 中海量数据的高效 API 消耗的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

热门话题

本文讨论了在浏览器中优化JavaScript性能的策略,重点是减少执行时间并最大程度地减少对页面负载速度的影响。

本文讨论了使用浏览器开发人员工具的有效JavaScript调试,专注于设置断点,使用控制台和分析性能。

Python和JavaScript开发者的薪资没有绝对的高低,具体取决于技能和行业需求。1.Python在数据科学和机器学习领域可能薪资更高。2.JavaScript在前端和全栈开发中需求大,薪资也可观。3.影响因素包括经验、地理位置、公司规模和特定技能。

本文说明了如何使用源地图通过将其映射回原始代码来调试JAVASCRIPT。它讨论了启用源地图,设置断点以及使用Chrome DevTools和WebPack之类的工具。

如何在JavaScript中将具有相同ID的数组元素合并到一个对象中?在处理数据时,我们常常会遇到需要将具有相同ID�...

深入探讨console.log输出差异的根源本文将分析一段代码中console.log函数输出结果的差异,并解释其背后的原因。�...
