【问题标题】:How exactly does the nodejs promise library work for multiple promises?nodejs Promise 库究竟是如何为多个 Promise 工作的?
【发布时间】:2014-10-19 20:06:21
【问题描述】:

最近我使用“promise”在 nodejs 中制作了一个网络爬虫。我为每个要抓取的 url 创建了一个 Promise,然后使用了 all 方法:

var fetchUrlArray=[];
for(...){
    var mPromise = new Promise(function(resolve,reject){
        (http.get(...))()
    });
    fetchUrlArray.push(mPromise);
}
Promise.all(fetchUrlArray).then(...)

有数以千计的网址,但只有少数网址超时。我的印象是它一次并行处理 5 个 Promise。 我的问题是 promise.all() 究竟是如何工作的。可以: 一个一个地调用每个promise,然后切换到下一个,直到前一个解决。 或者正在处理数组中的一批承诺。 或者它会触发所有的承诺

在 nodejs 中解决此问题的最佳方法是什么。因为就目前而言,我可以在 Java/C# 中更快地解决这个问题

【问题讨论】:

标签: javascript node.js parallel-processing web-scraping promise


【解决方案1】:

您传递给Promise.all() 的是一系列承诺。它对这些承诺背后的内容绝对一无所知。它所知道的是,这些 Promise 将在未来某个时候被解决或拒绝,并且它将创建一个新的主 Promise,该主 Promise 遵循您通过它的所有 Promise 的总和。这是关于 Promise 的好处之一。它们是一种抽象,可让您协调任何类型的操作(通常是异步的),而无需考虑它是什么类型的操作。因此,承诺实际上与实际行动无关。他们所做的只是监控行动的完成或错误,并将其报告给遵循承诺的代理。其他代码实际运行该操作。

在您的特定情况下,您会立即在紧密循环中调用 http.get(),并且您的代码(与 Promise 无关)会立即启动无数个 http.get() 操作。这些将被触发,只要底层传输可以完成它们(可能受连接限制)。

如果您希望它们连续启动或一次分批启动 10 个,那么您必须自己编写代码。承诺与此无关。

您可以使用 Promise 来帮助您编写代码以串行或批量启动,但无论哪种方式都需要额外的代码才能实现。

Async 库是专门为并行运行而构建的,但在任何给定时间都有最大运行数量,因为这是一种常见的方案,你要么有连接限制,要么你不想使接收服务器不堪重负。您可能对 parallelLimit 选项感兴趣,该选项可让您并行运行多个异步操作,但在任何给定时间都有最大数量的运行。

【讨论】:

  • 写得很好的答案。道具。值得一提的是,虽然 JavaScript 像在单线程中运行一样,但实际上 I/O 是异步执行的,并且实际上是在另一个线程上执行的。还值得一提的是,您在 Bluebird 中有一个 {concurrency: X} 选项。
【解决方案2】:

我会这样做

就个人而言,我不是 Promise 的忠实粉丝。我认为 API 非常冗长,生成的代码很难阅读。下面定义的方法会生成非常扁平的代码,并且更容易立即理解发生了什么。至少在我看来。

这是我为回复this question而创建的一个小东西

// void asyncForEach(Array arr, Function iterator, Function callback)
//   * iterator(item, done) - done can be called with an err to shortcut to callback
//   * callback(done)       - done recieves error if an iterator sent one
function asyncForEach(arr, iterator, callback) {

  // create a cloned queue of arr
  var queue = arr.slice(0);

  // create a recursive iterator
  function next(err) {

    // if there's an error, bubble to callback
    if (err) return callback(err);

    // if the queue is empty, call the callback with no error
    if (queue.length === 0) return callback(null);

    // call the callback with our task
    // we pass `next` here so the task can let us know when to move on to the next task
    iterator(queue.shift(), next);
  }

  // start the loop;
  next();
}

你可以这样使用

var urls = [
  "http://example.com/cat",
  "http://example.com/hat",
  "http://example.com/wat"
];

function eachUrl(url, done){
  http.get(url, function(res) {
    // do something with res
    done();
  }).on("error", function(err) {
    done(err);
  });
}

function urlsDone(err) {
  if (err) throw err;
  console.log("done getting all urls");
}

asyncForEach(urls, eachUrl, urlsDone);

这样做的好处

  • 没有外部依赖或 beta api
  • 可在您想要执行异步任务的任何阵列上重复使用
  • 非阻塞,正如您对 node 所期望的那样
  • 可以轻松适应并行处理
  • 通过编写自己的实用程序,您可以更好地了解这种事情的工作原理

如果您只想获取一个模块来帮助您,请查看 asyncasync.eachSeries 方法。

【讨论】:

  • 这比 Promise 版本慢得多,也更容易出错。它没有正常工作的堆栈跟踪,并且它不能很好地组合或传播错误。此外,如果您忘记在某个地方显式调用.done,应用程序可能会挂起,您可能不知道原因。 Promise 解决了这一切。此外 - 正确承诺的代码会看起来更好。
  • 你在说什么。 “慢点”?用什么衡量标准?堆栈跟踪非常清晰,并且可以在 done 处理程序中处理错误。为什么你会“忘记”打电话给.done?这是一段异步代码。如果有的话,更明确的要求比隐含的要求更好。
  • 哦,我记得你,Promises Evangelist。感谢您对我的代码的主观评价。
【解决方案3】:

首先,澄清一下:promise 确实代表了计算的未来结果,仅此而已。它不代表任务或计算本身,这意味着它不能被“调用”或“解雇”。

您的脚本确实会立即创建所有这数千个 promise,并且每个创建都会立即调用 http.get。我怀疑http 库(或它所依赖的东西)有一个连接池,限制了并行发出的请求数,并隐式推迟了其余请求。

Promise.all 不做任何“处理”——它不负责启动任务和解决通过的承诺。它只监听它们并检查它们是否都准备好了,并为最终结果返回一个承诺。

【讨论】:

    猜你喜欢
    • 2018-03-03
    • 1970-01-01
    • 1970-01-01
    • 2012-06-12
    • 2017-10-20
    • 2011-06-26
    • 2021-08-15
    • 2012-06-08
    • 2011-10-11
    相关资源
    最近更新 更多