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

Posted

技术标签:

【中文标题】node.js 模块:Async vs Fibers.promise vs Q_oper8【英文标题】:node.js modules: Async vs Fibers.promise vs Q_oper8 【发布时间】:2012-05-04 04:47:11 【问题描述】:

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

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

【问题讨论】:

【参考方案1】:

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

纤维的缺点

其他 javascript 开发者不熟悉

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

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

不支持 Windows

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

浏览器不兼容

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

更难调试

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

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

基本上,我认为协程 concept 变得更难使用,因为它必须存在于 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.auto 控制流方法。 (您指定一系列命名函数并列出它依赖执行的其他函数(如果有),auto 首先运行“独立”函数,然后根据其依赖函数完成的时间运行下一个可以运行的函数正在运行。)

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

总结

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

【讨论】:

哇,谢谢!这无疑给了我很多思考。我现在是关闭时间,但我今晚会考虑一下,如果我有任何问题,明天再来。 我想我现在真的明白了。您的论点归结为您在摘要中所说的简单内容:我正在寻找的功能是异步提供的,因此没有理由用协同程序来混淆语言。如果我在 javascript 领域拥抱函数式范式生活,那将是繁荣的 :-) 谢谢谢谢! 不客气。我并不是说协同程序不好。只是它们在 Javascript 中很糟糕,因为您同时有协程和事件发生,并且任何使用 Fiber 的复杂协程都必须混合它们(任何 I/O 操作) 然后你会遇到一个奇怪的情况,两个协同程序同时运行并以意想不到的方式影响彼此的范围。 Go 中的协同程序,在事件循环之外,更容易理解,并且因此具有更少的细微错误。 也许不是这种情况,但现在 Windows 上支持光纤。 程序员不熟悉不是一个好的论据。在某些时候,每个人都对一切都不熟悉,它们只是因为有用而变得流行。【参考方案2】:

我在客户端使用 jQuery 的 Deferred 功能,在服务器上使用 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/

【讨论】:

它根本没有回答这个问题。你只是在说你用什么,你没有对他要求的东西进行比较。【参考方案3】:

简短的回答:

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 有更多用于管理异步代码的选项,因此如果您有兴趣,请查看the documentation。

输入光纤/期货

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

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

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

回到上面的同一个异步示例,这就是 Fiber/Futures 的样子:

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

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

浏览器不兼容

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

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

调试难度更大

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

在 javascript 中引入先发制人

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

无效的抱怨

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

其他期货库

有许多实现期货的库,其中的概念可能被称为“期货”、“延迟对象”或“承诺”。这包括 async-future、streamline.js、Q、when.js、promiscuous、jQuery's deferred、coolaj86's futures、kriszyp's promises 和 Narrative 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 is single-threaded* 和 javascript 没有可用的本地线程,进程是利用 node.js 中多个处理器的常用方法。 Q_oper8 旨在替代使用 node.js 的 child_process 模块管理进程。

【讨论】:

【参考方案4】:

您还应该查看Step。

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

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

【讨论】:

以上是关于node.js 模块:Async vs Fibers.promise vs Q_oper8的主要内容,如果未能解决你的问题,请参考以下文章

node.js async.js nextTick vs setImmediate

node.js async.js nextTick vs setImmediate

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

利用async和await异步操作解决node.js里面fs模块异步读写,同步结果的问题

使用 node.js 函数 async.retry 确定成功/失败

Node.js async eachLimit 在这种情况下如何工作?