21

只是想知道是否有人可以比较这些模块之间处理异步事件的权衡。具体来说,我有兴趣了解使用 Async 而不是 Fibers.promise 的原因,我现在至少在我的测试代码中广泛使用它。特别是,我在 Fibers.promise 中看到的主要优点之一是我可以保持堆栈链前端分叉,从而可以使用try { } catch { } finally,并且还允许我确保在处理请求后结束响应。

有人在使用 Q_oper8 吗?我在另一页上找到了这个,只是想知道它是否已经死了,或者我应该检查一下。

4

4 回答 4

37

我从来没有听说过Q_oper8,所以我不能评论它,但我会从另一个方向来。我首先听说了异步,其次是 Fiber(及其辅助库),实际上我不喜欢后者。

纤维的缺点

其他 Javascript 开发人员不熟悉

Fiber 通过编译后的本机方法将协同程序的概念引入 Javascript,该Fiber方法接管传递给它的 Javascript 代码的解释,拦截调用以yield跳回等待的协同程序。

这对你来说可能无关紧要,但如果你需要在一个团队中工作,你必须向你的成员传授这个概念(或者希望他们对其他语言的概念有经验,比如 Go)。

不支持 Windows

因此,为了使用 Fiber 或任何基于它编写的库,您必须首先为您的平台本地编译它。我不使用 Windows,但请注意 Windows 不支持 Fiber,因此限制了您自己的库的实用程序。这意味着您根本找不到用 Fiber 编写的通用 Node.js 库(无论如何,您可能不会找到,因为它增加了一个昂贵的编译步骤,否则您可以通过异步避免)。

浏览器不兼容

这意味着您使用 Fiber 编写的任何代码都将无法在浏览器中运行,因为您无法将本机代码与浏览器混合(我作为浏览器用户也不希望您这样做),即使您编写的所有内容都是“Javascript "(它在语法上是 Javascript,但在语义上不是)。

更难调试

虽然“回调地狱”在视觉上可能不那么令人愉悦,但连续传递风格确实有一件非常好的事情,超过了协同例程——你可以从调用堆栈中确切地知道问题发生在哪里,并且可以向后追溯。协同程序在程序中不止一个点进入函数,并且可以从三种调用中退出:returnthrowyield(),其中后者也是返回点。

使用协同程序,您可以在“同时”运行的两个或多个函数之间进行交叉执行,并且您可能在事件循环上同时运行一组以上的协同程序。使用传统的回调,您可以保证函数的外部范围在执行所述函数期间是静态的,因此您只需在需要时检查这些外部变量一次。协程需要在每个之后运行这些检查yield()(因为它与原始协程的使用将被转换为真正的 Javascript 中的回调链)。

基本上,我认为协同程序的概念变得更难使用,因为它必须存在Javascript 事件循环中,而不是作为一种实现方法。

是什么让异步“更好”?

越差越好

实际上,这是一种“越差越好”的想法。Async 不是扩展 Javascript 语言来尝试摆脱它的缺陷(并创建新的,在我看来),而是一个纯 Javascript 解决方案来掩盖它们,就像化妆一样。

控制流显式

Async 函数描述了需要跨越事件循环障碍的不同类型的逻辑流,并且该库涵盖了实现该逻辑所需的回调代码的实现细节,您只需提供它应该以大致线性顺序运行的函数它们将在事件循环中执行。

如果您愿意放弃异步方法参数周围的第一个缩进级别,则与 Co-Routines 相比,您没有额外的缩进,并且只有少量额外的function(callback) {声明行,如下所示:

var async = require('async');
var someArray = [1, 2, 3, 4, 5, 6, 7, 8, 9];
async.forEach(someArray,
function(number, callback) {
    //Do something with the number
    callback();
}, function(err) {
    //Done doing stuff, or one of the calls to the previous function returned an error I need to deal with
});

在这种情况下,您知道您的代码使用的所有变量如果没有被您的代码更改,则只能在您的代码运行之前更改,因此您可以更轻松地调试,并且只有一种“返回”机制: callback(). 您要么在成功时不进行任何回调,要么在出现问题时将错误传递给回调。

代码复用不难

上面的示例使代码重用变得困难,但并非必须如此。您始终可以将命名函数作为参数传递:

var async = require('async');

// Javascript doesn't care about declaration order within a scope,
// so order the declarations in a way that's most readable to you

async.forEach(someArray, frazzleNumber, doneFrazzling);

var someArray = [1, 2, 3, 4, 5, 6, 7, 8, 9];

function frazzleNumber(number, callback) {
    // Do something to number
    callback();
}

function doneFrazzling(err) {
    // Do something or handle error
}

功能性而非强制性

async 模块不鼓励使用命令式流控制,并鼓励(要求,对于跨事件循环的部分)使用函数进行流控制。

函数式风格的优点是您可以轻松地重用循环体或条件,并且您可以创建更好地匹配代码流的新控制流“动词”(通过async library),例如async.auto为函数调用顺序实现依赖图解析的控制流方法。(您指定一系列命名函数并列出它依赖于执行的其他函数(如果有),并auto首先运行“独立”函数,然后根据其依赖函数完成运行的时间运行下一个可以运行的函数。)

与其编写代码以适应您的语言规定的命令式风格,不如按照问题的逻辑编写代码,并实现“胶水”控制流以使其发生。

总之

Fiber 就其扩展 Javascript 语言的本质而言,无法在 Node.js 中开发大型生态系统,尤其是当 Async 在外观部门获得 80% 的份额时,并且没有 Javascript 中协同程序的其他缺点。

于 2012-04-23T20:03:46.817 回答
12

简短的回答:

  • Async是用于管理单线程异步的纯/经典 javascript 解决方案
  • Fibers是一个用于创建协程的 node.js 扩展。它包括一个用于管理单线程异步的期货库。
  • 还有许多其他期货库(如下所列)不需要扩展 javascript。
  • Q_oper8是一个 node.js 模块,用于管理多进程并发

请注意,这些都没有提供“线程”,因此可以说没有一个可以执行多线程(尽管也有一个 node.js 扩展名:threads_a_gogo)。

异步与光纤/期货

异步

Async 和 Fibers/futures 是解决同一问题的不同方法:管理异步解决依赖关系。与许多其他试图解决这个问题的库相比,异步似乎有更多的“花里胡哨”,在我看来,这使情况变得更糟(更多的认知开销——即更多的废话要学习)。

在 javascript 中,基本异步如下所示:

asyncCall(someParam, function(result) {
   useThe(result);
});

如果你的情况需要的不仅仅是基本的异步,比如你需要两个异步调用的结果,你可能会做这样的事情:

asyncCall1(someParam, function(result0) {
  asyncCall2(someParam, function(result1) {
   use(result0, result1);
  }
});

已经开始看起来像回调地狱。它的效率也很低,因为第二次调用正在等待第一次调用完成,即使它不依赖于它,更不用说代码甚至没有进行任何合理的错误处理。Async 提供了一种更有效地编写它的解决方案:

async.parallel([
  function(callback) {
    asyncCall1(someParam, function(result0) {
      callback(null,result0);
    },
  function(callback) {
    asyncCall1(someParam, function(result1) {
      callback(null,result1);
    },
  }
],
function(err, results) {
  use(results[0], results[1]);
});

所以对我来说,这比回调地狱更糟糕,但我想对每个人来说都是他自己的。尽管它很丑陋,但它允许两个调用同时发生(只要它们进行非阻塞 IO 调用或类似的东西)。Async 有更多用于管理异步代码的选项,因此如果您有兴趣,请查看文档

输入光纤/期货

Fibers 模块的协程包括一个期货库,该库使用协程将异步事件重新注入到当前的延续中(future.wait())。

Fibers 与大多数其他期货库不同,因为它允许当前继续等待异步事件 - 这意味着它不需要使用回调来从异步请求中获取值 - 允许异步代码成为类似同步的。阅读有关协程的更多信息。

Node.js 具有诸如 readFileSync 之类的 io 函数,它可以让您在它为您获取文件的同时在线等待函数。这不是通常在 javascript 中完成的事情,也不是可以用纯 javascript 编写的东西——它需要像 Fibers 这样的扩展。

回到上面相同的异步示例,这就是使用 Fiber/Future 时的样子:

var future0 = asyncCall1(someParam);
var future1 = asyncCall2(someParam);
use(future0.wait(), future1.wait());

这与那里的异步混乱一样简单且高效。它以一种优雅有效的方式避免了回调地狱。虽然有(小)缺点。大卫埃利斯夸大了许多缺点,所以我将在这里重复唯一有效的一个:

浏览器不兼容

由于 Fibers 是 node.js 扩展,它不会与浏览器兼容。这将使 node.js 服务器和浏览器无法共享使用光纤的代码。但是,有一个强有力的论点是,您想要在服务器上的大多数异步代码(文件系统、数据库、网络调用)与您想要在浏览器上的代码(ajax 调用)不同也许超时会发生冲突,但这似乎是这样。

除此之外,streamline.js项目有能力弥补这一差距。似乎它有一个编译过程,可以将使用同步和期货的 streamline.js 代码转换为使用回调样式的纯 javascript,类似于现在不支持的Narrative Javascript。Streamline.js 可以在幕后使用几种不同的机制,一种是 node.js Fibers,另一种是 ECMAScript 6 生成器,最后一种是我已经提到的回调样式 javascript 的转换。

更难调试

这似乎是一个有效的,如果轻微的,抱怨。即使您只是计划使用纤程/期货,而不是将协程用于其他任何事情,由于意外的函数退出(和入口)点,上下文切换可能仍然会令人困惑。

将先发制人引入 javascript

这可能是纤维最大的问题,因为它有可能(尽管不太可能)引入难以理解的错误。基本上,由于 Fiberyield可能导致一组代码临时退出到另一个未确定的函数,因此可能会读取或引入一些无效状态。有关更多信息,请参阅本文。就个人而言,我认为纤维/期货和类似结构令人难以置信的清洁度非常值得那些罕见的阴险虫子。更多的错误是由糟糕的并发代码引起的。

无效的抱怨

  • 不在 Windows 上:这不再是真的
  • 不熟悉协程: A. 不熟悉永远不是回避某事的理由。如果它好它就很好,不管你对它有多熟悉。B. 虽然协程和产量可能不熟悉,但期货是一个容易理解的概念。

其他期货库

有许多实现期货的库,其中的概念可能被称为“期货”、“延迟对象”或“承诺”。这包括async-futurestreamline.jsQwhen.jspromiscuousjQuery 的 deferredcoolaj86 的 futureskriszyp 的 promisesNarrative Javascript等库。

其中大多数使用回调来解决期货问题,从而解决了 Fibers 引入的许多问题。但是,它们不像光纤/期货那样干净,但它们比异步要干净得多。这是同样的例子,再次使用我自己的async-future

var future0 = asyncCall1(someParam);
var future1 = asyncCall2(someParam);
Future.all([future0, future1]).then(function(results) {
  use(results[0], results[1])
}).done()

Q_oper8

Q_oper8 真的是一个不同的野兽。它使用进程池在队列中运行作业。由于javascript 是单线程*,并且 javascript 没有可用的本地线程,因此进程是在 node.js 中利用多个处理器的常用方法。Q_oper8 旨在作为使用 node.js 的 child_process 模块管理进程的替代方案。

于 2013-06-03T00:33:46.207 回答
2

您还应该查看Step

它只处理 async 可以做的一小部分,但我认为代码更容易阅读。它非常适合处理做一系列事情的正常情况,其中一些事情是并行发生的。

我倾向于将 Step 用于我的大部分逻辑,然后在我需要在串行或并行执行中重复应用方法时偶尔使用 async(即 - 调用此函数直到,或在此数组的每个元素上调用此函数)。

于 2013-11-21T03:21:38.933 回答
1

我在客户端上使用 jQuery 的 Deferred 功能,在服务器上为 nodejs 使用 jQuery Deferred 来代替嵌套回调。它大大减少了代码并使事情变得如此可读。

http://techishard.wordpress.com/2012/05/23/promises-promises-a-concise-pattern-for-getting-and-showing-my-json-array-with-jquery-and-underscore/

http://techishard.wordpress.com/2012/05/29/making-mongoose-keep-its-promises-on-the-server/

于 2012-05-29T17:18:46.420 回答