【发布时间】:2016-06-20 03:26:20
【问题描述】:
注意:我在 Node.js 上运行了这段代码
我正在玩弄一个假的“非阻塞”函数,以便了解如何为 Node.js 编写异步函数。在这样做的时候,我遇到了这个我不太理解的场景。
下面我有一个“假”异步函数的代码,它接受一个回调函数和一个计数器。该函数将递归调用自身 [counter] 次,然后执行提供的回调函数。每 10 次迭代,它就会将控制权释放回事件队列。
原文
//fakeCounter helps us keep track of whether or not the operation is "complete"
//fn is the callback function that will be executed when the operation is complete
function asyncOp(fn, fakeCounter){
//if fakeCounter is less than or equal to zero, that means the
//operation is "complete" and you can call the callback now
if(fakeCounter <= 0) return fn();
console.log("another [" + fakeCounter + "] cycles to go");
//every 10 or so iterations, release control
if(fakeCounter % 10 === 0){
process.nextTick(asyncOp(fn,--fakeCounter));
}else{
asyncOp(fn,--fakeCounter);
}
}
asyncOp(function (){
console.log("operation complete");
}, 10000);
如上所示,我通过使用 process.nextTick 来释放控制权。但是,当我执行脚本时,我得到了这个错误:
原始结果
another [10000] cycles to go
another [9999] cycles to go
another [9989] cycles to go
...
another [3] cycles to go
another [2] cycles to go
another [1] cycles to go
operation complete
node.js:415
callback();
^
TypeError: undefined is not a function
at process._tickCallback (node.js:415:13)
at Function.Module.runMain (module.js:499:11)
at startup (node.js:119:16)
at node.js:902:3
这个错误和堆栈跟踪对我来说几乎没有意义,所以我有一段时间摸不着头脑。
但是,当我修改 asyncOp 函数使其通过 setTimeout 释放控制时,不会引发此类错误:
修改功能
function asyncOp(fn, fakeCounter){
if(fakeCounter <= 0) return fn();
console.log("another [" + fakeCounter + "] cycles to go");
if(fakeCounter % 10 === 0){
setTimeout(asyncOp(fn, --fakeCounter)); //<-- see here
}else{
asyncOp(fn,--fakeCounter);
}
}
修订结果
...
another [3] cycles to go
another [2] cycles to go
another [1] cycles to go
operation complete
并且没有出现任何错误。
...所以,我的问题是:为什么使用 process.nextTick() 会引发 TypeError,但使用 setTimeout() 却不会?为什么一开始会引发 TypeError?
编辑:
如果将来有人读到这篇文章,正确的答案,正如下面提到的@m02ph3u5,是将函数调用包装在一个匿名函数中:
if(fakeCounter % 10 === 0){
process.nextTick(function(){asyncOp(fn, --fakeCounter)}).
}else{
asyncOp(fn,--fakeCounter);
}
但是,在这个特定的示例中,最好使用 setImmediate 而不是 process.nextTick,因为显然递归 process.nextTick 调用将在下一版本的 node.js 中被破坏。此外,在这种情况下使用 process.nextTick() 将导致 RangeError。因此,正确的解决方案是使用以下内容:
if(fakeCounter % 10 === 0){
setImmediate(function (){
asyncOp(fn,--fakeCounter)
});
}else{
asyncOp(fn,--fakeCounter);
}
谢谢@m02ph3u5 和@mscdex
【问题讨论】:
-
process.nextTick(asyncOp(fn,--fakeCounter));将执行asyncOp()并将结果传递给process.nextTick()。这是你想要的吗? -
啊,不,这不是我想要的。我打算让 process.nextTick() 使用提供的参数简单地运行 asyncOp()。
-
这么想 ;) 改为传递一个匿名函数。例如。
nextTick(function(){asyncOp(fn, --fakeCounter)}) -
Heya @m02ph3u5 - 谢谢。这是一个愚蠢的错误。但是,当我 确实 传递了一个匿名函数时,我收到了以下错误:(node) 警告:检测到递归 process.nextTick。这将在下一个版本的节点中中断。请使用 setImmediate 进行递归延迟。 RangeError: 超出最大调用堆栈大小。 之后,我自然而然地将 process.nextTick() 调用替换为 setImmediate() 调用,这当然有效。但是,为什么 setImmediate() 没有触发 RangeError?我应该为此提出一个新问题吗? :P
标签: javascript node.js