【问题标题】:Combine results from multiple Node.js API calls合并来自多个 Node.js API 调用的结果
【发布时间】:2017-06-21 05:09:40
【问题描述】:

这里是 Node.js 的新手。我正在寻找从另一个函数中进行 N 个异步 API 调用的正确方法,并将它们的结果结合起来以进一步使用下游。在我的情况下,N 会相当小,并且阻塞它们的执行也不会太糟糕。

在同步执行中,下面的 combine() 实现应该可以工作。

如果我只需要一个 API 调用的结果,那么在提供给 callAPI() 的回调函数中实现以下逻辑将很简单。我绊倒的地方是我需要在执行 foo(total, [...args]) 之前合并所有结果。

我查看了async.whilst,但无法让它发挥作用。我怀疑这是否真的适合我的需求。我还研究了Promises,这似乎是正确的线索,但在爬进那个海绵状的兔子洞之前得到保证会很好。不管Promises是正确的方式,哪个模块是Node.js项目中使用的标准?

var http = require('http');

function callAPI(id) {
    var options = {
        host: 'example.com',
        path: '/q/result/'.concat(id)
    }

    var req = http.get(options, (res) => {
        var body = [];
        res.on('data', (chunk) => {
            body.push(chunk);
        }).on('end', () => {
            body = Buffer.concat(body).toString();
            return body;
        }).on('error', (err) => {
            console.error(err);
        });
    });
}

function combine(inputs) {

    var total = 0;
    for (i=0; i < inputs.length; i++) {
        total += callAPI(inputs[i]['id']);
    };
    console.log(total);

    // call some function, foo(total, [...args])
}

编辑 1:

我尝试按照下面 samanime 的回答修改 API 调用以返回 Promise。见:

function callAPI(id) {
    return Promise((resolve, reject) => {
        var options = {
            host: 'example.com',
            path: '/q/result/'.concat(id)
        }

        var req = http.get(options, (res) => {
            var body = [];
            res.on('data', (chunk) => {
                body.push(chunk);
            }).on('end', () => {
                body = Buffer.concat(body).toString();
                resolve(body);
            }).on('error', (err) => {
                reject(err);
            });
        });
    });
}

function combine(inputs) {

    var combined = [];
    for (i=0; i < inputs.length; i++) {
        total += callAPI(inputs[i]['id']);
            .then(result => {
                combined.push(result);
            });
    };
    var total = combined.reduce((a, b) => a + b, 0);
    console.log(total);

    // call some function, foo(total, [...args])
}

这似乎让我走到了一半。如果我在then() 块内console.log(combined),我可以看到列表由API 调用的结果组成。但是,我仍然无法在for 循环的“结束”处访问完整的combined。我可以在构建完整列表后将回调附加到要运行的内容吗?有没有更好的办法?

编辑 2(我的解决方案 - 根据 Patrick Roberts 的建议)

function callAPI(id) {
    return Promise((resolve, reject) => {
        var options = {
            host: 'example.com',
            path: '/q/result/'.concat(id)
        }

        var req = http.get(options, (res) => {
            var body = [];
            res.on('data', (chunk) => {
                body.push(chunk);
            }).on('end', () => {
                body = parseInt(Buffer.concat(body));
                resolve(body);
            }).on('error', (err) => {
                reject(err);
            });
        });
    });
}

function combine(inputs) {
    var combined = [];
    Promise.all(inputs.map(input => callAPI(input.id)))
        .then((combined) => {
            var total = combined.reduce((a, b) => a + b, 0);
            // foo(total, [...args])
        });
};

【问题讨论】:

标签: javascript node.js


【解决方案1】:

听起来您可以将一堆 Promise 链接在一起,传递数据。

基本上是这样的:

const combined = [];
asyncOne()
  .then(result => { combined.push(result); return asyncTwo())
  .then(result => { combined.push(result); return asyncThree())
  // and so on

只要每个函数都返回一个承诺,你就万事大吉了。

如果你想并行运行它们,请使用Promise.all(),它会为你做同样的事情:

Promise.all([asyncOne(), asyncTwo(), asyncThree() /* , etc */])
  .then(combined => /* combined is an array with the results of each */)

这是迄今为止这类事情的首选模式。

【讨论】:

    【解决方案2】:

    您的编辑看起来好多了,但试试这个:

    function callAPI(id) {
      return Promise((resolve, reject) => {
        var options = {
          host: 'example.com',
          path: '/q/result/' + id
        }
    
        http.get(options, (res) => {
          var body = [];
          res.on('data', (chunk) => {
            body.push(chunk);
          }).on('end', () => {
            body = Buffer.concat(body).toString();
            resolve(body);
          }).on('error', reject);
        });
      });
    }
    
    function combine(inputs) {
      Promise.all(inputs.map(input => callAPI(input.id))).then((combined) => {
        // completed array of bodies
        console.log(combined);
        // foo(combined.length, [...args]);
      }).catch((error) => {
        console.log(error);
      });
    }
    

    【讨论】:

      【解决方案3】:

      我会添加一个计数器来跟踪剩余的 API 调用。每当 API 调用完成时,递减,如果为 0,则完成。

      const numCalls = 10;
      let remaining = numCalls;
      let data = [];
      
      function getRandomInt(min, max) {
        min = Math.ceil(min);
        max = Math.floor(max);
        return Math.floor(Math.random() * (max - min)) + min;
      }
      
      function ajax() {
          // Simulate ajax with a setTimeout for random amount of time.
          setTimeout(() => {
              // This is the callback when calling http.get
              data.push(getRandomInt(0, 10)); // Some data from server
              if (--remaining <= 0) {
                  // Am I the last call? Use data.
                  console.log(data);
                  console.log(data.length);
              }
          }, getRandomInt(1000, 3000));
      }
      
      for (let i = 0; i < numCalls; i++) {
          ajax();
      }

      【讨论】:

      • 将应用程序逻辑与异步控制流逻辑混合通常是一种不好的做法,这就是创建 Promises 以及在它们之前创建 async 等库的原因。
      • 当然。这一切都取决于正确的。如果可以,请使用 Promise。但是,我会争辩说,在代码中的某些点调用 resolve/reject 是异步控制流逻辑(但更简洁),我们仍在将它与 app.逻辑。
      • 我说的是counting 内部回调,以及条件 在应用程序逻辑中执行消费者回调。当然,您必须resolve()reject()callback()next(),具体取决于您选择使用的异步控制流程,但这比您自己尝试重新发明轮子要干净得多控制流逻辑。
      猜你喜欢
      • 2018-06-22
      • 1970-01-01
      • 2018-02-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-25
      • 2022-01-19
      • 1970-01-01
      相关资源
      最近更新 更多