nodejs递归调用相同的api并按顺序写入excel文件

时间:2018-02-15 15:50:12

标签: javascript node.js promise request-promise

我需要在获取API的结果后需要使用请求承诺递归调用API,需要在excel文件中写入,下面给出API样本响应

{
"totalRecords": 9524,
"size": 20,
"currentPage": 1,
"totalPages": 477,
"result": [{
        "name": "john doe",
        "dob": "1999-11-11"
    },
    {
        "name": "john1 doe1",
        "dob": "1989-12-12"
    }

]
}

现在我想调用这个API n次,这里n等于totalPages,在调用每个API之后我想将响应结果写入excel文件。 首先将第1页的响应结果写入excel,然后将第2页的响应结果附加到excel文件,依此类推。 我已经写了一些示例代码,如下所示

function callAPI(pageNo) {
var options = {
  url: "http://example.com/getData?pageNo="+pageNo,
  method: 'GET',
  headers: {        
    'Content-Type': 'application/json'
  },
  json: true
}   
return request(options)
}

callAPI(1).then(function (res) {
   // Write res.result to excel file      
}).catch(function (err) {
// Handle error here
})

但是面对问题调用递归API和顺序维护就像写第1页结果首先到excel文件然后第2页结果追加到excel等等。 任何代码示例如何在nodejs中实现

3 个答案:

答案 0 :(得分:1)

你想做这样的事情:

function getAllPages() {
    function getNextPage(pageNo) {
        return callAPI(pageNo).then(response => {
            let needNextPage = true;
            if (pageNo === 1) {
                // write to file
            } else {
                // append to file
            }

            if (needNextPage) {
                return getNextPage(pageNo+1);
            } else {
                return undefined;
            }
        });
    }

    return getNextPage(1);
}

显然要改变“需要下一页”'在你完成时停止递归

答案 1 :(得分:1)

所以你想按顺序做477个请求?你要等多久才能完成这个?即使是在并列中,这对我来说仍然太长了。

最佳:编写一个可以一次返回一批页面的API。减少对后端的请求数量。也许像http://example.com/getData?pages=1-100之类的东西让它返回一个数组;也许像

[
  {
    "totalRecords": 9524,
    "currentPage": 1,
    "totalPages": 477,
    "result": [...]
  },

  {
    "totalRecords": 9524,
    "currentPage": 2,
    "totalPages": 477,
    "result": [...]
  },
  ...
]

或更紧凑

{
  "totalRecords": 9524,
  "totalPages": 477,
  "pages": [
    {
      "currentPage": 1,
      "result": [...]
    },

    {
      "currentPage": 2,
      "result": [...]
    },
    ...
  ]
}

旁注:size数组的results写入json是不必要的。可以从data.result.length

轻松确定此值

但回到你的问题

伊莫。您想按顺序运行的只是将页面添加到工作表中。请求可以在并列中完成。这已经为整个任务节省了大量的整体运行时间。

callApi(1).then(firstPage => {
  let {currentPage, totalPages} = firstPage;

  //`previous` ensures that the Promises resolve in sequence, 
  //even if some later request finish sooner that earlier ones.
  let previous = Promise.resolve(firstPage).then(writePageToExcel);

  while(++currentPage <= totalPages){
    //make the next request in paralell
    let p = callApi(currentPage);

    //execute `writePageToExcel` in sequence
    //as soon as all previous ones have finished
    previous = previous.then(() => p.then(writePageToExcel));
  }
  return previous;
})
.then(() => console.log("work done"));

或等待所有页面加载,然后再将它们写入excel

callApi(1).then(firstPage => {
  let {currentPage, totalPages} = firstPage;

  let promises = [firstPage];
  while(++currentPage < totalPages)
    promises.push(callApi(currentPage));

  //wait for all requests to finish
  return Promise.all(promises);
})
//write all pages to excel
.then(writePagesToExcel)
.then(() => console.log("work done"));

或者您可以批量处理请求

callApi(1).then(firstPage => {
  const batchSize = 16;
  let {currentPage, totalPages} = firstPage;

  return Promise.resolve([ firstPage ])
    .then(writePagesToExcel)
    .then(function nextBatch(){
      if(currentPage > totalPages) return;

      //load a batch of pages in paralell
      let batch = [];
      for(let i=0; i<batchSize && ++currentPage <= totalPages; ++i){
        batch[i] = callApi(currentPage);
      }

      //when the batch is done ...
      return Promise.all(batch)
        //... write it to the excel sheet ...
        .then(writePagesToExcel)
        //... and process the next batch
        .then(nextBatch);
    });
})
.then(() => console.log("work done"));

但不要忘记添加错误处理。由于我不确定您是如何处理我发布的方法的错误,因此我没有在此处包含错误处理。

编辑:

  

你可以修改批处理请求,得到一些错误,你在哪里分配toalPages,这是不正确的,为什么totalPages应该等于firstPage

let {currentPage, totalPages} = firstPage;
//is just a shorthand for
let currentPage = firstPage.currentPage, totalPages = firstPage.totalPages;
//what JS version are you targeting?

第一个请求callApi(1).then(firstPage => ...)主要用于确定currentIndextotalLength,因为您在返回的JSON中提供了这些属性。现在我知道了这两个,我可以按照我的意愿在并列中发起尽可能多的请求。而且我不必等待他们中的任何一个完成以确定我的索引,并且还有更多的页面要加载。

  

以及为什么要撰写return Promise.resolve([ firstPage ])

为了省去一些麻烦和检查,因为我对你如何实施writePagesToExcel一无所知。
return Promise.resolve(...)所以我可以做.then(writePagesToExcel)。这解决了我两个问题:

  1. 我不必关心writePagesToExcel返回同步或承诺,我可以随时跟进另一个.then(...)

  2. 我不需要关心writePagesToExcel可能会抛出。如果出现任何错误,它们都会在Promise链中结束,并且可以在那里进行处理。

  3. 所以最终我通过简单地将firstPage包裹在Promise中并继续使用.then(...)来保护自己一些检查。考虑到您在此处理的数据量,imo。这不是太多的开销,以摆脱一些潜在的陷阱。

      

    为什么要像解决方案一样传递数组

    在每个例子中保持一致。在这个例子中,我命名了处理数据writePagesToExcel (复数)的函数,它应该表明它处理多个页面(它们的数组);我认为在这种情况下这很清楚。

    因为我仍然需要在开始时单独调用以获得firstPage,并且我不想使nextBatch中的逻辑复杂化只是为了将第一页连接到第一页,我对待[firstPage]作为单独的“批处理”,将其写入Excel并继续nextBatch

答案 2 :(得分:-1)

function callAPI(pageNo) {
  var options = {
    url: "http://example.com/getData?pageNo="+pageNo,
    method: 'GET',
    headers: {        
      'Content-Type': 'application/json'
    },
    json: true
  }   
  return request(options)
}

function writeToExcel(res){console.log(res)} //returns promise.


callAPI(1).then(function (res) {
   if(res){
       writeToExcel(res).then(() => {
          var emptyPromise = new Promise(res => setTimeout(res, 0));
          while(res && res.currentPage < res.totalPages){
             emptyPromise = emptyPromise.then(() => {
               return callAPI(res.currentPage).then(function (res){
                   if(res){
                     writeToExcel(res)
                   }
               });
             }
          }
          return emptyPromise; 
       });
   }
}).catch(function (err) {
// Handle error here
})