【问题标题】:task.js generators/promises vs. async callbackstask.js 生成器/承诺与异步回调
【发布时间】:2013-03-07 12:34:30
【问题描述】:

我想知道在处理 JavaScript 中的异步代码时,这两种方法中哪一种更好。我想了解哪种方法会导致代码更清晰。我使用了 promise,它们似乎比异步方法更灵活(https://github.com/caolan/async)。

我也知道 Task.js 库 (http://taskjs.org/),但这依赖于 Ecmascript Harmony 中的 yield 关键字。

【问题讨论】:

  • 使用链接的异步库的主要优点是它包装/提供异步风格的许多常见操作。虽然可以使用“普通”Promises(即 jQuery.Deferred),但它需要更多样板文件(如果需要异步样式中的其中一个操作),因为您实际上必须编写自己的上述函数版本.链接的异步库和 Promises 最终以相同的方式工作 - 使用 callback
  • 我喜欢 Promise 的地方是你从一个函数返回一些东西,而不是接受一个你稍后会调用的回调(对我来说这就像说:我现在不能把这个值返回给你,但我向你保证,你会得到一个值)。我不喜欢的是你必须传递 2 个函数,但是当 EcmaScript Harmony 准备好时,这将得到解决。鉴于 Task.js 非常棒,promise 确实是未来的证明。也许开发一个模块来缩小 Promise 和回调之间的差距(类似于异步,但返回一个 Promise)会很有趣。

标签: javascript node.js asynchronous


【解决方案1】:

async 库封装了几个非常常见的异步模式,包括并行进行任意异步调用和异步迭代列表。它旨在与“nodeback”(err, res) API 一起使用,这使得它对许多 Node.js 应用程序都很有用。然而async 是一个特定 解决方案,它只简化了库中包含的异步模式。

相比之下,在我看来,Promises 是针对异步代码问题的更通用的解决方案。乍一看,它们不仅提供了错误冒泡和扁平化回调金字塔的明显好处,而且可以更简单地解决原本需要各种复杂模式 async 封装的问题。

我将通过快速浏览async 的一些可用模式来演示这一点。例如,async.waterfall 函数是这样使用的:

async.waterfall([
      function (cb) {
        asyncCall('argument', cb);
      },
      function(resultOfFirstCall, cb) {
        anotherCall(resultOfFirstCall, 'someOtherArgument' cb);
      },
    ], function(err, res) {
      if (err) handle(err);
      useFinalResult(res);
    });

在大多数 Promise 库中没有与 async.waterfall 等效的东西(或者至少在 Q 中没有),因为使用 Array.reduce 从头开始​​实现它是如此简单,就像这样(基于 Q 的示例,但在其他 Promise 库中几乎相同):

[
  function() {
    return asyncCall('argument');
  },
  function(resultOfFirstCall) {
    return anotherCall(resultOfFirstCall, 'someOtherArgument');
  }
].reduce(Q.when, Q())
 .then(useFinalResult, handle);

async中的其他大函数包括async.parallel,其中Q包括Q.all

// async
async.parallel([
    asyncFunc,
    asyncFunc2
    ], function(err, res) {
      if (err) handle(err);
      useFinalResult(res);
      // res[0] === asyncFuncResult
      // res[1] === asyncFunc2Result
    });
// Q
Q.all([
  asyncFunc(),
  asyncFunc2()
]).then(useFinalResult, handle);

还有async.map。当您使用 Promise 时,您实际上不需要 async.map,因为普通的 Array.map 就足够了:

// async
async.map(['file', 'file2', 'file3'], fs.stat, function(err, res) {
  if (err) handle(err);
  useFinalResult(res);
});
// Q
Q.all(['file', 'file2', 'file3']
  .map(Q.nfbind(fs.stat)))
  .then(useFinalResult, handle);

async 的其余部分同样易于简洁地实现,只需使用您的 Promise 库中相对简单的部分即可。 (请注意,最后一个示例使用了一个函数 Q.nfbind: nfbind 和 Q 提供的其他 nf* 函数基本上是您在 nodeback API 中使用 Promise 所需的全部内容,因此尝试使用时甚至没有特别大的阻力对期望 nodebacks 的库的承诺。)

最后,是否使用 Promise 或 nodebacks 取决于您,但我认为 Promise 是一种更灵活、更有能力且通常更简洁的方式来实现大多数异步操作。

Callbacks are imperative, promises are functional 值得一读,以了解更多关于这方面的信息。

【讨论】:

    【解决方案2】:

    gumballhead 推荐 async.js,但如果您使用 Node.js,i 会推荐使用 Parse Cloud Code。他们的 API 内置了 Promise,以及其他好东西(如数据库)。它节省了时间,您不必担心后端的稳定性。您可以包含任何 NPM 模块,只需将 module.exports 调整为 exports。它还与您的前端无缝集成!我在当前项目中使用这种方法取得了成功,只是想用一种新方法发表评论。

    请随意评论您为什么/何时应该使用 Cloud Code;因为我还没有这样的经历。

    【讨论】:

      【解决方案3】:

      我认为promise/a和async lib有不同的target,promise专注于一步异步操作进度,而async专注于多步异步操作,对于node来说,async对于很多async api有更广泛的用途。

      顺便说一句,要处理异步操作,使用命名函数而不是匿名函数将是最有效的方法

      【讨论】:

        【解决方案4】:

        由于您使用 node 标记了您的问题,我建议您使用异步库。控制流函数非常适合使用并消除了丑陋且难以遵循的回调链。该 API 的设置非常适合将遵循节点签名 (error, result) 的回调注入到控制函数中。它基本上默认包含在我编写的几乎所有节点脚本中。

        虽然您也可以将异步用于客户端,但对于大多数项目来说可能没有必要。 jQuery 包含 Promise,你可以用它们完成同样的事情。

        【讨论】:

          猜你喜欢
          • 2018-02-13
          • 2018-03-02
          • 2012-12-17
          • 2020-10-05
          • 1970-01-01
          • 2019-08-17
          • 2018-02-16
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多