首页 web前端 js教程 JavaScript 中海量数据的高效 API 消耗

JavaScript 中海量数据的高效 API 消耗

Oct 20, 2024 pm 08:42 PM

Efficient API consumption for huge data in JavaScript

使用处理大型数据集的 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中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌
威尔R.E.P.O.有交叉游戏吗?
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

如何创建和发布自己的JavaScript库? 如何创建和发布自己的JavaScript库? Mar 18, 2025 pm 03:12 PM

文章讨论了创建,发布和维护JavaScript库,专注于计划,开发,测试,文档和促销策略。

如何在浏览器中优化JavaScript代码以进行性能? 如何在浏览器中优化JavaScript代码以进行性能? Mar 18, 2025 pm 03:14 PM

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

前端热敏纸小票打印遇到乱码问题怎么办? 前端热敏纸小票打印遇到乱码问题怎么办? Apr 04, 2025 pm 02:42 PM

前端热敏纸小票打印的常见问题与解决方案在前端开发中,小票打印是一个常见的需求。然而,很多开发者在实...

如何使用浏览器开发人员工具有效调试JavaScript代码? 如何使用浏览器开发人员工具有效调试JavaScript代码? Mar 18, 2025 pm 03:16 PM

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

谁得到更多的Python或JavaScript? 谁得到更多的Python或JavaScript? Apr 04, 2025 am 12:09 AM

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

如何使用源地图调试缩小JavaScript代码? 如何使用源地图调试缩小JavaScript代码? Mar 18, 2025 pm 03:17 PM

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

如何使用JavaScript将具有相同ID的数组元素合并到一个对象中? 如何使用JavaScript将具有相同ID的数组元素合并到一个对象中? Apr 04, 2025 pm 05:09 PM

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

console.log输出结果差异:两次调用为何不同? console.log输出结果差异:两次调用为何不同? Apr 04, 2025 pm 05:12 PM

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

See all articles