Node.js 异步库比较 - Q 与 Async

Posted

技术标签:

【中文标题】Node.js 异步库比较 - Q 与 Async【英文标题】:Node.js Asynchronous Library Comparison - Q vs Async 【发布时间】:2014-04-24 23:33:15 【问题描述】:

我在一个项目(网络爬虫/人类活动模拟器)中使用了kriskowal's Q library,并且熟悉了 Promise、返回和解析/拒绝它们,以及库的基本异步控制流方法和错误抛出/捕获机制已被证明是必不可少的。

虽然我遇到了一些问题。我的promise.then 电话和我的回调具有形成金字塔的不可思议的趋势。有时是为了范围的原因,有时是为了保证一定的事件顺序。 (我想我也许可以通过重构来解决其中的一些问题,但接下来我想完全避免“回调地狱”。)

此外,调试非常令人沮丧。我花了很多时间console.log-寻找错误和错误的来源;在我终于找到它们之后,我会开始在那里抛出错误并使用promise.finally 在其他地方捕获它们,但是首先定位错误的过程是艰巨的。

另外,在我的项目中,订单很重要。我需要按顺序做几乎所有事情。很多时候我发现自己会生成返回 promise 的函数数组,然后使用 Array.prototype.reduce 将它们相互链接起来,我认为我不应该这样做。

以下是我使用这种缩减技术的方法之一的示例:

removeItem: function (itemId) 

  var removeRegexp = new RegExp('\\/stock\\.php\\?remove=' + itemId);

  return this.getPage('/stock.php')
  .then(function (webpage) 
    var
      pageCount = 5,
      promiseFunctions = [],
      promiseSequence;

    // Create an array of promise-yielding functions that can run sequentially.
    _.times(pageCount, function (i) 
      var promiseFunction = function () 
        var
          promise,
          path;

        if (i === 0) 
          promise = Q(webpage);
         else 
          path = '/stock.php?p=' + i;
          promise = this.getPage(path);
        

        return promise.then(function (webpage) 
          var
            removeMatch = webpage.match(removeRegexp),
            removePath;

          if (removeMatch !== null) 
            removePath = removeitemMatch[0];

            return this.getPage(removePath)
            .delay(1000)
            // Stop calling subsequent promises.
            .thenResolve(true);
          

          // Don't stop calling subsequent promises.
          return false;

        .bind(this));
      .bind(this);

      promiseFunctions.push(promiseFunction);
    , this);

    // Resolve the promises sequentially but stop early if the item is found.
    promiseSequence = promiseFunctions.reduce(function (soFar, promiseFunction, index) 
      return soFar.then(function (stop) 
        if (stop) 
          return true;
         else 
          return Q.delay(1000).then(promiseFunction);
        
      );
    , Q());

    return promiseSequence;
  .bind(this))
  .fail(function (onRejected) 
    console.log(onRejected);
  );
,

我还有其他方法可以做基本相同的事情,但会遇到更严重的压痕问题。

我正在考虑使用coalan's async library 重构我的项目。它似乎与 Q 相似,但我想确切地知道它们有何不同。我得到的印象是 async 更“以回调为中心”,而 Q 是“以承诺为中心”。

问题:考虑到我的问题和项目要求,使用 async over Q 会获得什么和/或失去什么?图书馆如何比较? (特别是在顺序执行一系列任务和调试/错误处理方面?)

【问题讨论】:

要求顺序执行似乎抵消了异步的大部分好处。 如果你展示了一段你现在正在使用的特别笨拙的代码,而你想要一个更好的解决方案,人们可​​能会为你提供更好的建议。抽象地讨论不同库的优缺点或如何使用这些库要困难得多。 @jfriend00 我同意;我添加了一个代码示例。 可以通过正确使用 .then 来减少/移除“金字塔”。另一方面,异步库有几种方法可用于同步进行异步调用,例如.series.eachSeries。 .then 链当然可以实现同样的目标 @Robert Harvey - 在节点中,异步仍然很有价值,因为它为其他请求提供了处理的机会 【参考方案1】:

两个库都很好。我发现它们有不同的用途,可以串联使用。

Q 为开发人员提供了 promise 对象,它们是值的未来表示。对时间旅行很有用。

Async 为开发人员提供了控制结构和聚合操作的异步版本。

一个 linter 实现尝试的示例展示了库之间的潜在统一性:

function lint(files, callback) 

    // Function which returns a promise.
    var getMerged = merger('.jslintrc'),

        // Result objects to invoke callback with.
        results = [];

    async.each(files, function (file, callback) 
        fs.exists(file, function (exists) 

            // Future representation of the file's contents.
            var contentsPromise,

                // Future representation of JSLINT options from .jslintrc files.
                optionPromise;

            if (!exists) 
                callback();
                return;
            

            contentsPromise = q.nfcall(fs.readFile, file, 'utf8');
            optionPromise = getMerged(path.dirname(file));

            // Parallelize IO operations.
            q.all([contentsPromise, optionPromise])
                .spread(function (contents, option) 
                    var success = JSLINT(contents, option),
                        errors,
                        fileResults;
                    if (!success) 
                        errors = JSLINT.data().errors;
                        fileResults = errors.reduce(function (soFar, error) 
                            if (error === null) 
                                return soFar;
                            
                            return soFar.concat(
                                file: file,
                                error: error
                            );
                        , []);
                        results = results.concat(fileResults);
                    
                    process.nextTick(callback);
                )
                .catch(function (error) 
                    process.nextTick(function () 
                        callback(error);
                    );
                )
                .done();
        );
    , function (error) 
        results = results.sort(function (a, b) 
            return a.file.charCodeAt(0) - b.file.charCodeAt(0);
        );
        callback(error, results);
    );

我想为每个文件做一些潜在的阻塞。所以async.each 是显而易见的选择。我可以将相关操作每次迭代q.all并行化,如果它们适用于2个或更多文件,我可以重用我的选项值。

这里,Async 和 Q 各自影响程序的控制流,Q 代表未来某个时间解析为文件内容的值。这些库可以很好地协同工作。一个不需要“选择一个而不是另一个”。

【讨论】:

【参考方案2】:

可以使用 Promise 组合和 javascript 词法范围来简化代码中的回调金字塔。

removeItem: function (itemId) 

  var removeRegexp = new RegExp('\\/stock\\.php\\?remove=' + itemId);
  var found = false
  var promise = getPage('/sock.php')

  _.times(5, (i) => 
    promise = promise.then((webpage) => 
      if (found) return true
      var removeMatch = webpage.match(removeRegexp)
      var found = removeMath !== null
      var nextPage = found ? removeMatch[0] : '/stock.php?p='+i+1
      return Q.delay(1000).then(() => this.getPage(nextPage))
    )
  )

  return promise.fail(console.log.bind(console))

,

恕我直言 async 不应在新的 javascript 代码中使用。 Promise 更具可组合性,并允许编写更直观的代码。

Node 不使用 Promise 的主要原因是性能问题,Bluebird 和 Q 等库在很大程度上已经很好地解决了这些问题。

随着 async/await 语法变得越来越主流,promise 将为看起来与同步代码非常相似的代码铺平道路。

【讨论】:

【参考方案3】:

虽然这仍然不是我的问题(Q vs async)的实际答案,但关于我的问题,我发现Selenium / WebDriverJs 是一个可行的解决方案。

driver.get('http://www.google.com');
driver.findElement(webdriver.By.name('q')).sendKeys('webdriver');
driver.findElement(webdriver.By.name('btnG')).click();
driver.wait(function() 
  return driver.getTitle().then(function(title) 
    return title === 'webdriver - Google Search';
  );
, 1000);

WebDriver 使用队列顺序执行承诺,这极大地有助于控制缩进。它的 Promise 也与 Q 兼容。

创建一系列承诺不再是问题。一个简单的 for 循环就可以了。

至于在序列中提前停止,不要这样做。不要使用序列,而是使用异步设计和分支。

【讨论】:

我必须说我从未想过使用 selenium 进行抓取,我将它用作人类活动模拟器进行测试,不过这很有趣。此外,webdriver promise 与 Q 不兼容,它们的评估方式不同,可能会给您带来误报。如果您需要创建自定义承诺,请使用 webdriver.promise()。

以上是关于Node.js 异步库比较 - Q 与 Async的主要内容,如果未能解决你的问题,请参考以下文章

Node.js异步库async

node.js 模块:Async vs Fibers.promise vs Q_oper8

Node.js 与 .net 中的异步/等待

Node.js 同步与异步

重构嵌套回调、node.js、异步

转学习使用 Node.js 中 async-hooks 模块