0

在从 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 中实现

4

3 回答 3

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": [...]
    },
    ...
  ]
}

旁注:将数组写入 json 是不必要的sizeresults这个值可以很容易地从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 链中结束,并且可以在那里得到处理。

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

为什么你要像解析一样传递数组

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

由于我在开始时仍然需要这个单独的调用来获取firstPage,并且我不想nextBatch仅仅为了将第一页与第一批连接起来而使逻辑复杂化,所以我将[firstPage]其视为单独的“批处理”,将其写入 excel 并继续和nextBatch

于 2018-02-15T17:43:49.337 回答
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);
}

显然,将“needNextPage”更改为 false 以在完成后停止递归

于 2018-02-15T16:14:37.133 回答
-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
})
于 2018-02-15T16:14:33.723 回答