如何在Node.js中创建一个非阻塞异步函数?

25

我该如何创建一个非阻塞的异步函数?以下是我想要实现的内容,但我的程序仍然是阻塞状态...

var sys = require("sys");

function doSomething() {
  sys.puts("why does this block?");
  while(true);
}

setTimeout(doSomething,0);
setTimeout(doSomething,0);
setTimeout(doSomething,0);

sys.puts("main");

给定的示例将在永远运行的 while 循环中阻塞。另请参阅 https://dev59.com/b1sW5IYBdhLWcg3wk38q - Marcus
10个回答

35

这篇文章是从Reddit转载而来。


JavaScript中异步函数的目的与您所寻求的有些不同。

请记住,JavaScript是单线程的——它一次只能做一件事情。以下是一些传统的阻塞代码:

sys.puts("Before");
sleep(10);
sys.puts("After");

在现实的网络应用程序中,sleep() 可能会变成耗时的数据库调用、网络请求(比如等待用户的Web浏览器传输数据)、辅助工具或文件访问。

如果使用像上述那样的阻塞调用,Node.js 服务器将无法在等待期间执行其他任何操作(例如开始处理其他网络请求)。

PHP 和许多其他 Web 编程环境通过为每个请求创建完全独立的线程来处理此问题。Node.js 使用回调函数。您可以按照以下方式重写相同的代码:

sys.puts("Before");
setTimeout(function(){
    sys.puts("After");
}, 10000);

在这里,您创建一个函数并将其传递给 setTimeout()。它的代码尚未运行,但是当它运行时,它将可以访问创建它的所有作用域(所有变量)。setTimeout() 获取对该函数的引用,并安排在超时到期后在事件循环上触发事件。

事件循环本质上就是 Node.js 程序的待办事项列表(它们很常见——运行在计算机上的所有 GUI 应用程序可能都使用事件循环!)。

在调用 setTimeout() 后,当前函数继续执行。最终返回,调用它的函数也返回等等,直到程序回到事件循环中。事件循环查看是否有任何事情发生(例如传入请求),而您的代码正在执行,并调用您的代码中适当的函数。如果没有,则等待直到发生某些事情(例如超时到期)。

异步代码不会让您的代码同时执行多个任务,但是当某些代码依赖于外部因素才能继续时,可以消除阻塞。

在您的 Node.js 程序中很少需要执行阻塞工作。如果确实需要,请将该工作分离到另一个进程中(甚至可以是另一个 Node.js 程序),或编写C/C++插件,它可以自由使用线程。


1
啊!现在我更清楚地理解了。感谢您的解释;您倒数第三段尤其有用。+1 - Paul d'Aoust

17

在node.js中,您只能使用由node.js运行时提供的异步IO函数或使用用C ++编写的node.js扩展来进行异步IO。您自己的Javascript代码始终是同步的。异步非IO代码很少需要,并且node.js的设计者决定避免使用它。

还有一些晦涩的方法可以在node.js中异步运行Javascript,其他评论者提到过,但是node.js并不是为这种类型的工作而设计的。如果您需要这种类型的并发性,请使用Erlang,node.js仅支持并行IO,对于并行计算,它与Python或PHP一样糟糕。


很好的答案,我有一个问题:在Node.js中,MongoDB操作是异步IO吗? - Dhiral Pandya
1
是的,例如.connect.insert都是异步函数。请参见http://mongodb.github.io/node-mongodb-native/2.0/overview/quickstart/。 - nponeccop

11

27
理解 Node 的重要概念是,它并不要求函数一直异步执行,而是关注 I/O 操作的异步非阻塞执行。如果你的函数没有涉及到 I/O 操作,Node 就不能帮助你实现异步执行。但是,Node 提供了工具帮助你达成这个目标,比如子进程。这就是 Felix 回答中所指的内容。你的函数可以创建一个子进程来执行任务,这样就可以脱离主事件循环独立执行。 - Marco

6
如果您不想使用仍处于alpha版本的WebWorker API / node-worker,只需创建一个额外的节点程序并通过TCP或HTTP进行通信。
这允许您将工作分派为HTTP调用或原始TCP数据,并异步等待HTTP响应/传入TCP答案。
但请注意,仅当您的任务易于串行化时才适用。

4

setTimeout 不会创建新的线程,因此浏览器仍然会在无限循环中挂起。

您需要重新思考程序结构。


你有任何想法如何在node.js中实现这个吗?谢谢。 - Richard
1
这个问题很老了,但对像我一样的node.js初学者仍然非常有趣。如上所述,node.js是用于并行I/O而不是并行工作的。这意味着:如果您有一个耗时的工作要做来响应单个请求,node.js将无法加速您在该单个请求中尝试实现的内容。使用sleep()和setTimeout()之间的区别在于,当使用sleep()进行睡眠时,node.js将阻止所有其他请求 - 使用setTimeout()将不会阻止其他请求。或者说:10个请求到sleep(10s)将需要100秒 - 10个请求到setTimeout(10s)只需要10秒。 - schlicki

2
你可以使用nextTick()而不使用子进程来完成这个任务。
我前几天刚读到关于nextTick的解释... 在我看来,这是很好的一篇文章。
---编辑:旧链接已失效。在StackOverflow上找到了一个新的链接。 理解node.js事件队列和process.nextTick

该链接已失效。 - slipperypete

1
如果我理解你的意思,你有两个函数(都需要一个回调作为参数): process.nextTick: 这个函数可以被堆栈化,也就是说,如果它被递归调用,它会在事件循环的每个周期内循环执行一次(最多 process.maxTickDepth 次)。
或者 setImmediate: 这个函数与设置 0 延时非常接近(但在之前发生)。
在大多数情况下,你应该优先选择 setImmediate

0

我假设你正在尝试使用Nodejs,同时也假设你没有编写任何npm模块本身,并将你的函数导出为异步函数。实际上,你不会遇到这种情况,如果你想在函数或回调中执行阻塞操作,这些操作在单线程环境中执行,请尝试使用适当的节点模块,如fs用于文件读取,request用于网络请求和async模块,它提供了各种类型的异步操作。此外,请记住,尝试异步执行消耗CPU的代码是一个坏主意,因为2HZ可以在不到微秒的时间内执行一百万行代码。

Nodejs的主要目的是解决你在问题中提出的问题,这由底层libuv处理异步事件来处理。我赞赏使用set time out的答案,但你不知道要等多长时间,使用node worker也可以,但你最终会编写大量样板代码,所以只需搜索满足你需求的npm模块即可。


0

尝试基于事件循环模型(node.js的核心)回答这个问题。

setTimeout(doSomething,0)。是的,setTimeout会将任务添加到任务队列中。在0秒后,时间到了。事件循环检查任务队列并发现任务已完成,其回调函数doSomething已准备好运行。

上述过程是异步的。

需要注意的一件事是:回调函数doSomething在node.js的主线程(或单个线程)上运行,事件循环也在该线程上运行。毫无疑问,while(true)循环完全阻塞了该线程。


-6

这一行:

while(true);

不是“阻塞”,只是忙碌,永远。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接