首页 > 解决方案 > 如何使用 Node JS 执行多个 API 调用

问题描述

我正在围绕 Eve Online 的 ESI api 建立一个站点。我正在使用 NodeJS 使用 axios 进行异步获取调用。我正在尝试使用他们的 api 获取各种游戏区域的市场数据。我已经成功地获取数据并将其添加到我的数据库中,但我觉得我可以以更好更快的方式进行这些调用。他们的 api 在页面中返回结果,每页有 1000 个结果。对于某些地区,我必须请求多达 300 多页,但通常是 100 页。我一直在使用 for 循环执行此操作,然后只检查结果数组是否有数据,如果没有,我会中断 for 循环。对于某些地区,这仅需要几秒钟 10-20 页,而对于其他地区则需要几分钟 50 多页。我正在努力加快速度。现在下载所有内容并上传到我的数据库大约需要 60 分钟,

这是我当前获取数据的代码。我首先得到一个包含所有游戏区域 ID 的列表,然后使用 for 循环遍历每个区域。使用当前区域 ID,我使用它来获取该区域中所有类型的项目 ID。这是我的第一页问题出现的地方。每个区域可以有 1 件商品或 15000 件商品。所以我选择 50 作为任意数字循环直到,检查每个调用是否返回数据。然后我使用相同的区域 ID 执行相同的操作来获取所有订单。这是我的下载时间受到影响的地方。一些地区的订单很少或更多,其中一个地区有 300 多页,这在我的笔记本电脑上需要 12 分钟,而在 AWS 设置上需要 4-5 分钟。

       const regionData = await axios.get(
                "https://esi.evetech.net/latest/universe/regions/?datasource=tranquility"
            );
//Eg region 10000042
            const region = regionData.data;
            console.log(region);
            for (let r = 0; r < region.length; r++) {
                let typeID = [];
                let orders = [];
        
                for (let i = 1; i < 50; i++) {
                    const types = await axios.get(
                        `https://esi.evetech.net/latest/markets/${region[r]}/types/?datasource=tranquility&page=${i}`
                    );
                    if (types.data.length) {
                        for (let x = 0; x < types.data.length; x++) {
                            typeID.push(types.data[x]);
                        }
                    } else {
                        break;
                    }
                }
    
                for (let i = 1; i < 500; i++) {
                    const results = await axios.get(
                        `https://esi.evetech.net/latest/markets/${region[r]}/orders/?datasource=tranquility&order_type=all&page=${i}`
                    );
                    if (results.data.length) {
                        for (let x = 0; x < results.data.length; x++) {
                            orders.push(results.data[x]);
                        }
                    } else {
                        break;
                    }
                }
    
    ... code to sort through data and upload to database
    
    }

我想加快这个下载过程,这样我就不必等待每个页面一次下载一个。

我正在尝试使用异步承诺一次下载多个页面,但由于每个区域的页面数量不同,我刚刚选择了一个适合所有人的最大数量,这导致承诺一次拨打 300 多个电话,这只是中断并导致 500 错误。当我将页数限制为小于 100 时,它的效果非常好,并且可以在我的 for 循环所用时间的一小部分内获取该数据。我只是不知道如何在不尝试一次请求 300 页的情况下实现循环并获取所有数据的方法。

这是我正在测试的当前代码,它使用为类型和订单映射一组 api 链接的承诺。当它少于 100 页时效果很好,但更多我开始收到 500 个服务器错误。

let urlsR = [];
    let urlsT = [];
    
    const getAllData = async (URLs) => {
        return Promise.all(URLs.map(fetchData));
        
    };
    function fetchData(URL) {
        return axios
            .get(URL)
            .then(function (response) {
                return response.data;
            })
            .catch(function (error) {
                console.log(error)
                 //return { success: false };
            });
    }
    const regionData = await axios.get(
        "https://esi.evetech.net/latest/universe/regions/?datasource=tranquility"
    );
    const region = regionData.data;
    for (let r = 0; r < region.length; r++) {
        console.log("Starting API fetch for " + region[r]);
        urlsR = [];
        urlsT = [];
        for (let i = 1; i < 17; i++) {
            urlsT.push(
                `https://esi.evetech.net/latest/markets/${region[r]}/types/?datasource=tranquility&page=${i}`
            );
        }
        for (let i = 1; i < 316; i++) {
            urlsR.push(
                `https://esi.evetech.net/latest/markets/${region[r]}/orders/?datasource=tranquility&order_type=all&page=${i}`
            );
        }
        let getTypes = await getAllData(urlsT);
        let getMarket = await getAllData(urlsR);
        let orders = [];
        
... Code that sorts through data and uploads to DB
}

我的问题是无法检查每个区域需要请求多少页,因此我再次选择了一个上限,并且每个区域都针对 api 请求了这么多页。如果没有找到结果,api 只会返回一个空数组,这样可以避免因错误而破坏代码。我只是不想进行 100 次 api 调用,只获得 15 个页面和 85 个空数组,或者尝试一次执行 300 个并不断出错。

很抱歉漫无边际,但任何想法或方向都是有帮助的。

标签: javascriptnode.jsapies6-promise

解决方案


BluebirdsPromise.map包含并发限制。

const Promise = require('bluebird')

const getAllData = async (URLs) => {
  return Promise.map(URLs, fetchData, 20)
}

推荐阅读