【问题标题】:Why does process.nextTick() trigger a TypeError in this "fake asynchronous function", but setTimeout() doesn't?为什么 process.nextTick() 会在这个“假异步函数”中触发 TypeError,而 setTimeout() 不会?
【发布时间】: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


【解决方案1】:

原因是process.nextTick() 期望一个函数作为第一个参数,而您的asyncOp() 返回undefined(默认情况下)。 undefined 不是一个函数,所以它会抛出一个 TypeErrorsetTimeout() 目前不会立即检查其第一个参数的类型(不过,这将在节点 v6 中更改),因此它不会抛出错误。

【讨论】:

  • 如果将来有人读到这个,正确的答案,正如上面提到的@m02ph3u5,是将函数调用包装在一个匿名函数中。 process.nextTick(function(){asyncOp(fn, --fakeCounter)})。但是,在这种特定情况下,最好使用 setImmediate 而不是 process.nextTick,因为显然递归 process.nextTick 调用将在下一版本的 node.js 中被破坏
  • 其实在 node v4+ 中你可以不用匿名函数:process.nextTick(asyncOp, fn, --fakeCounter);
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-20
  • 1970-01-01
  • 1970-01-01
  • 2011-05-08
  • 2014-01-04
相关资源
最近更新 更多