【问题标题】:Array of filtered axios results from paginated API is empty来自分页 API 的过滤 axios 结果数组为空
【发布时间】:2021-06-05 03:33:05
【问题描述】:

在下面的代码中,我的console.log(response) 上有一个空数组,但getIds 函数内的console.log(filterdIds) 显示了我想要的数据。我觉得我的决心不对。

请注意,我运行do..while 一次进行测试。 API 是分页的。如果记录是昨天的,它将继续,如果不是,则do..while 将停止。

有人能指出正确的方向吗?

const axios = require("axios");

function getToken() {
    // Get the token
}

function getIds(jwt) {
    return new Promise((resolve) => {
        let pageNumber = 1;
        const filterdIds = [];

        const config = {
            //Config stuff
        };

        do {
            axios(config)
                .then((response) => {
                    response.forEach(element => {
                        //Some logic, if true then:
                        filterdIds.push(element.id);
                        console.log(filterdIds);
                    });
                })
                .catch(error => {
                    console.log(error);
                });
        } while (pageNumber != 1)
        resolve(filterdIds);
    });
}


getToken()
    .then(token => {
        return token;
    })
    .then(jwt => {
        return getIds(jwt);
    })
    .then(response => {
        console.log(response);
    })
    .catch(error => {
        console.log(error);
    });

由于do..while,我也不确定将拒绝放在getIds 函数中的哪个位置。

【问题讨论】:

标签: javascript node.js promise axios


【解决方案1】:

要退后一步想想您遇到此问题的原因,我们必须考虑同步和异步 javascript 代码如何协同工作。您的 同步getIds 函数将运行到完成,逐步执行每一行直到它到达末尾。

axios 函数调用返回一个Promise,它是一个代表未来实现或拒绝值的对象。 Promise 直到事件循环的下一个周期(最早)才会解决,并且您的代码告诉它做一些事情 when 返回挂起的值(即.then() 方法中的回调)。

但是您的主要 getIds 函数不会等待...它调用 axios 函数,为返回的 Promise 提供将来要做的事情,然后继续进行,越过 do/while 循环并进入 resolve 方法,该方法从您在函数开头创建的 Promise 返回一个值...但是 axios Promise 到那时还没有解决,因此 filterIds 还没有没有被填充。

当您将要创建的 Promise 的 resolve 方法移动到 axios 已解决的 Promise 将调用的回调中时,它开始工作因为现在您的 Promise 等待 axios 解决,然后再解决自己。

希望这能阐明您可以采取哪些措施来实现多页目标。


我不禁想到有一种更简洁的方法可以让您一次获取多个页面,然后如果最后一页表明还有其他页面要获取,则递归地继续获取。您可能仍需要添加一些额外的逻辑来过滤掉您批量获取但不符合您正在寻找的任何标准的任何页面,但这应该可以帮助您:

async function getIds(startingPage, pages) {
    const pagePromises = Array(pages).fill(null).map((_, index) => {
        const page = startingPage + index;
        // set the page however you do it with axios query params
        config.page = page;
        return axios(config);
    });

    // get the last page you attempted, and if it doesn't meet whatever
    // criteria you have to finish the query, submit another batch query
    const lastPage = await pagePromises[pagePromises.length - 1];
    
    // the result from getIds is an array of ids, so we recursively get the rest of the pages here
    // and have a single level array of ids (or an empty array if there were no more pages to fetch)
    const additionalIds = !lastPage.done ? [] : await getIds(startingPage + pages, pages);

    // now we wait for all page queries to resolve and extract the ids
    const resolvedPages = await Promise.all(pagePromises);
    const resolvedIds = [].concat(...resolvedPages).map(elem => elem.id);

    // and finally merge the ids fetched in this methods invocation, with any fetched recursively
    return [...resolvedIds, ...additionalIds];
}

【讨论】:

  • @ggorlen 提供了代码片段解决方案。但是由于您的解释,我终于明白为什么它不起作用了。我从来没有意识到 getIds 没有等待 axios 调用。我在这篇文章中阅读了多个参考资料,但这是迄今为止最容易理解的。非常感谢!
  • @remcoE33 任何一天我都会通过一个具体的例子来理解一个概念。 :) 乐于助人。
  • @remcoE33,如果您确实发现答案有帮助,我们将不胜感激。 :)
  • @RemcoE33 我添加了一个可能更易于使用的递归示例。
【解决方案2】:

根本问题是resolve(filterdIds);在请求触发之前同步运行,所以保证为空。

Promise.allPromise.allSettled 如果你知道你需要多少页(或者如果你使用块大小来发出多个请求——稍后会更多),可以提供帮助。这些方法并行运行。这是一个可运行的概念验证示例:

const pages = 10; // some page value you're using to run your loop

axios
  .get("https://httpbin.org") // some initial request like getToken
  .then(response => // response has the token, ignored for simplicity
    Promise.all(
      Array(pages).fill().map((_, i) => // make an array of request promisess
        axios.get(`https://jsonplaceholder.typicode.com/comments?postId=${i + 1}`)
      )
    )
  )
  .then(responses => {
    // perform your filter/reduce on the response data
    const results = responses.flatMap(response =>
      response.data
        .filter(e => e.id % 2 === 0) // some silly filter
        .map(({id, name}) => ({id, name}))
    );
    
    // use the results
    console.log(results);
  })
  .catch(err => console.error(err))
;
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

网络选项卡显示并行发生的请求:

如果页面数量未知,并且您打算一次触发一个请求,直到您的 API 通知您页面结束,则顺序循环很慢​​,但可以使用。 Async/await 更适合这种策略:

(async () => {
  // like getToken; should handle err
  const tokenStub = await axios.get("https://httpbin.org");
  
  const results = [];
  
  // page += 10 to make the snippet run faster; you'd probably use page++
  for (let page = 1;; page += 10) {
    try {
      const url = `https://jsonplaceholder.typicode.com/comments?postId=${page}`;
      const response = await axios.get(url);
      
      // check whatever condition your API sends to tell you no more pages
      if (response.data.length === 0) { 
        break;
      }
      
      for (const comment of response.data) {
        if (comment.id % 2 === 0) { // some silly filter
          const {name, id} = comment;
          results.push({name, id});
        }
      }
    }
    catch (err) { // hit the end of the pages or some other error
      break;
    }
  }
  
  // use the results
  console.log(results);
})();
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

这是顺序请求瀑布:

如果您想增加并行化,可以使用任务队列或分块循环。分块循环将结合这两种技术来一次请求n 记录,并检查块中的每个结果是否存在终止条件。这是一个去除过滤操作的简单示例,它是异步请求问题的附带问题,可以在响应到达后同步完成:

(async () => {  
  const results = [];
  const chunk = 5;
  
  for (let page = 1;; page += chunk) {
    try {
      const responses = await Promise.all(
        Array(chunk).fill().map((_, i) => 
          axios.get(`https://jsonplaceholder.typicode.com/comments?postId=${page + i}`)
        )
      );
      
      for (const response of responses) {      
        for (const comment of response.data) {
          const {name, id} = comment;
          results.push({name, id});
        }
      }
      
      // check end condition
      if (responses.some(e => e.data.length === 0)) { 
        break;
      }
    }
    catch (err) {
      break;
    }
  }
  
  // use the results
  console.log(results);
})();
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

(上图是100个请求中的一个例外,但一次5的块大小是可见的)

请注意,这些 sn-ps 是概念验证,可以不那么随意地捕获错误,确保捕获所有抛出等。将其分解为子功能时,请确保 .then 和 @ 987654341@调用者中的所有promise--不要试图把它变成同步代码。

另见

【讨论】:

  • 感谢您的回答。不幸的是,我不知道页面的数量。它是一个将最后一个订单放在顶部的 API。我在每页上得到 50 个结果。根据当天的订单数量,我有不同数量的页面。这就是为什么我有一段时间做,当我比昨天更晚的日期去做..while被打破了。
  • 底部示例基本上显示了这一点——只需使用for (let page = 1;; pages++) {} 并在它抛出或您的响应状态/数据表明没有更多可用结果时打破循环。 do-while 没有什么特别之处——它只是编写不经常使用的循环的一种更令人困惑的方式。查看更新。
  • 感谢您提供异步等待版本。还有你的洞察力。将阅读本主题中的所有参考资料。
  • 没问题,坚持下去——承诺需要一些时间来把握。
猜你喜欢
  • 1970-01-01
  • 2020-06-26
  • 2018-09-06
  • 1970-01-01
  • 2020-10-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多