在 node js 中使用 promise.all 进行外部 api 调用

Posted

技术标签:

【中文标题】在 node js 中使用 promise.all 进行外部 api 调用【英文标题】:Using promise.all to make external api calls in node js 【发布时间】:2020-06-07 02:09:57 【问题描述】:

我正在使用 request-promise npm 包在 nodeJS 中进行多个 api 调用。为了让我获得所需的数据,我必须遍历所有类别,然后在每个类别中循环遍历产品数组以获取产品信息。示例类别响应如下


        "success": true,
        "response": [
          
            "category_id": 9,
            "name": "Leather Wheeled luggage",
            "products": [
              "TL141911",
              "TL141888"
            ],
            "parent_category_id": 34
          ,
          
            "category_id": 10,
            "name": "Leather Luggage Weekender Duffles bags",
            "products": [
              "TL141794",
              "TL141658"

            ],
            "parent_category_id": 34
          
        

因为我必须循环并进行 api 调用,我正在尝试使用 Promise.all,但它没有产生正确的结果,它给我一个 404 not found 错误,有人可以帮我看看我在哪里这里出错了?以下是我尝试过的

const rp = require('request-promise');

const requestUrl = "https://stage.tuscanyleather.it/api/v1/";
const categoryRequestUrl = requestUrl + 'categories';
let tuscanApiOptions = 
    uri: categoryRequestUrl,
    headers: 
        'Authorization': 'Bearer asd343'
    ,
    method: 'Get',
    json: true 
;

rp(tuscanApiOptions)
    .then((categResponse) => 
        //res.end(categResponse);  
        let ps = [];
        categResponse.response.forEach((category) => 
            if (category.products !== undefined) 
                category.products.forEach((product) =>
                    let productApiOptions = 
                        uri: requestUrl + `product-info?code=$product`,
                        headers: 
                            'Authorization': 'Bearer asd343'
                        ,
                        method: 'Get',
                        json: true 
                    ;
                    ps.push(rp(productApiOptions));
                );
                Promise.all(ps)
                    .then((values) => 
                        console.log(values); //This is where things are going wrong
                  )
                    .catch((err) =>
                        console.log(JSON.stringify(err,null,2));
                    )
            

        )

    )
    .catch((error) => 
        console.log(error);
        //res.status(error.statusCode).send(error.error.error_description);
    );

【问题讨论】:

一小时前与您的其他问题相关:***.com/questions/60358803/… 我想,我没有正确地提出问题,所以我认为最好再发布一个包含更多信息的问题 你不应该有两个关于同一个基本主题的问题。当人们已经处理了您的问题并正在与您合作时,如果您想澄清问题,请使用“编辑”链接修改您的问题,而不仅仅是打开一个新问题。请删除这两个问题之一。 @jfriend00 我已经删除了另一个问题,谢谢 代码中显示的request 变量是request-promise 模块吗?如果是这样,请将其添加到您的代码中,以便显示。当我使用request-promise 模块(顺便说一句已弃用)时,我将其命名为不同的名称,例如rp,因此阅读我的代码的任何人都不会认为它是常规的request 模块。 【参考方案1】:

此代码应该可以帮助您调试 404:

const rp = require('request-promise');

const requestUrl = "https://stage.tuscanyleather.it/api/v1/";
const categoryRequestUrl = `$requestUrlcategories`;
const tuscanApiOptions = 
  uri: categoryRequestUrl,
  headers: 
    'Authorization': 'Bearer asd343'
  ,
  method: 'Get',
  json: true 
;

// Promise of flat array of products (loses category data)
const productsP = rp(tuscanApiOptions))
  .then((response) => response
    .map(category => category.products) // array of arrays
      .flat() // flatten to array of products
      .filter(p => !!p) // remove empty 
  )

const prodUrl = `$requestUrlproduct-info?code=`

const productDataP = productsP.then(products => 
  products.map(product => 
    rp(
      ...tuscanApiOptions,
      uri: `$prodUrl$product` 
    ).catch(e => `Error fetching $prodUrl$product $e.message`)
  )
)

// Wait for all requests to resolve with data or error. 
// resultsP is an array of Promise<productdata|error>
const resultsP = Promise.all(productsDataP)

resultsP.then(console.log)             

一些建议,接受或离开:

Array.forEach 从您的编程词汇中剔除。它会给你带来比它解决的问题更多的问题,因为它是副作用的(本质上是一个循环)。使用Array.map 返回一个新数组,然后对其进行操作。 Array.map 将状态机与数据转换分开,forEach 将它们混合在一起。

不要将letvar 用于不会发生变异的事物。这告诉其他程序员和机器不要依赖这个值。使用const

请参阅this article 以获得更深入的解释。

使用async/await 可能更容易做到这一点,至少在调试方面是这样。你有数据结构遍历和异步 monads (Promise),你需要在这里解决,还有一个 404 需要调试。 awaiting 一个 promise 将值扩展到 monad 上下文之外,因此它消除了一层复杂性。

当你有一个 Promises 数组时,你 await Promise.all(arrayP) 会得到一个未包装值的数组。

const rp = require('request-promise');

const requestUrl = "https://stage.tuscanyleather.it/api/v1/";
const categoryRequestUrl = `$requestUrlcategories`;
const tuscanApiOptions = 
  uri: categoryRequestUrl,
  headers: 
    'Authorization': 'Bearer asd343'
  ,
  method: 'Get',
  json: true 
;

async function main() 
  const categories = await rp(tuscanApiOptions)
  const products = categories.response
    .map(category => category.products) 
    .flat() 
    .filter(p => !!p) // remove undefined

  console.log(products) // should be flat array of products

  const prodUrl = `$requestUrlproduct-info?code=`

  const requestsP = products
    .map(product => 
      rp(
        ...tuscanApiOptions,
        uri: `$prodUrl$product` 
      ).catch(e => `Error fetching $prodUrl$product $e.message`)
  )

  const results = await Promise.all(requestsP)

  console.log(results)


main()   

【讨论】:

对于优秀的 forEach,这是一个相当极端的立场。我认为你的 let 和 var 建议在“信号”未来的读者方面是好的。也许我们应该允许 forEach 保留在语言中,作为正在应用程序而不是函数的信号。 查看this article 中的“自定义状态机:循环”部分,看看您的想法。把loop替换成forEach,因为是同一个东西,唯一的区别是forEach封装了计数器。 好文章!它应该被转换(映射!)到必修本科CS课程的教学大纲。但我仍然不相信循环。考虑您给出的示例,计算数组中的 Num。 Map 及其变体(如过滤器,当然更漂亮)产生一个中间数组和不需要的第二次迭代。孔子不是也说过“不乱丢”、“看不到不就没有”、“万里长路从一步开始,没有横着走”吗? ?? :-) 在过去几周回答了一堆关于 JS 和节点标签的问题后,我确信这是一把足枪。 :-P 像这个问题一样 - 状态管理、异步和数据转换的交集让您感到疲倦并受到复杂性的影响。在推断要调试之后,您将没有任何脑力。而且您的代码非常脆弱,您不敢触摸它,因为它可能会破坏状态机。

以上是关于在 node js 中使用 promise.all 进行外部 api 调用的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的 apolloFetch 调用在从 promise.all 中调用时返回一个空查询?

Promise.all() 与等待

Promise.all() 不等待异步进程

Node.js / Javascript 多重递归承诺在视图中返回

如何将 URL 中的数组作为参数传递给 Promise.all

Promise.all()使用方法