当前位置:嗨网首页>书籍在线阅读

10-创建一个异步回调函数

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

[toc]

2.3.2 创建一个异步回调函数

为了演示回调函数的基本结构,请看例2-5。例2-5是一个完整的Node应用程序,它创建了一个对象,该对象有一个函数 doSomething() 。这个函数有两个参数:第一个参数必须是一个数字,第二个参数是一个回调函数。在 doSomething() 中,如果第一个参数不存在或者不是数字,那么该对象将创建一个新的 Error 对象,并在回调函数中返回。如果没有发生错误,则调用回调函数,将错误设置为 null ,并返回数据计算结果(在本例中将返回第一个参数的计算结果)。

例2-5中的关键是回调功能,已用粗体标出。

例2-5 上一个回调函数的基本结构

var fib = function (n) {
    if (n < 2) return n;
    return fib(n - 1) + fib(n - 2);
}; 
var Obj = function() { };
Obj.prototype.doSomething = function(arg1_) {
   var callback_ = arguments[arguments.length - 1];
   callback = (typeof(callback_) == 'function' ? callback_ : null); 
   var arg1 = typeof arg1_ === 'number' ? arg1_ : null; 
   if (!arg1) 
      return callback(new Error('first arg missing or not a number'));
      process.nextTick(function() {
         // block on CPU
         var data = fib(arg1); 
         callback(null, data); 
   }); 
} 
var test = new Obj();
var number = 10;
test.doSomething(number, function(err,value) {
      if (err)
         console.error(err);
      else
         console.log('fibonaci value for %d is %d', number, value);
});
console.log('called doSomething');

异步回调函数的第一个关键点是确保最后一个参数是一个回调函数,并且回调函数中的第一个参数是一个错误对象。如第1章所述,这种错误优先模式通常被称为 errback 。我们无法知道用户的意图,但是我们可以确保最后一个参数是一个函数,而且必须如此。

第二个关键点是在发生错误时创建新的 Error 对象,并将其作为结果返回给回调函数。在异步的世界中,我们不能依赖 throw … catch ,所以错误处理必须在回调函数的 Error 对象中进行。

最后一个关键点是在没有发生错误时调用回调函数,并传递函数的数据。不过,为了确保这种回调是异步的,我们需要在 process.nextTick() 函数中调用它。原因是 process.nextTick() 方法可以确保在调用函数之前清除事件循环。这意味着在调用阻塞功能(如果有的话)之前,所有的同步功能都已经被处理。在这个例子中,发生阻塞的地方不是I/O,而是那些CPU密集型的操作。调用值为10的斐波纳契序列函数可能不需要花太多时间,但是调用值为50或者更大数字的函数就可能需要一些时间,这取决于你的操作系统。斐波纳契函数就是在 process.nextTick() 中调用的,从而确保CPU密集型的功能被异步处理。

简而言之,只要这几个关键点被满足,其他一切都是可变的:

  • 确保最后一个参数是一个回调函数;
  • 当有错误发生时,创建一个 Node Error 对象并将它作为回调函数的第一个参数返回;
  • 如果没有错误,就调用回调函数,将 error 参数设为 null ,并传入相关数据;
  • 回调函数必须在 process.nextTick() 中被调用,从而确保进程不被阻塞。

如果把 number 的值改成10,那么程序就会在控制台中打印如下信息:

called doSomething
[Error: first argument missing or not a number]

如果你去看Node安装的lib目录中的代码,那么将看到上面讲过的回调模式重复出现。虽然功能可能会改变,但这种模式保持不变。

这种方法非常简单,并且能确保异步方法的结果是一致的。

**回调嵌套** 使用回调函数很简单,但是也存在问题,其中包括回调的深度嵌套。我会在3.4.1节中讲深度嵌套和其解决方案。

之前我提到 http.Server 对象继承自另一个对象,我们从该对象获得了触发事件的能力。这个对象有一个很形象的名字: EventEmitter ,接下来我们将详细介绍它。

在大多数情况下,Node是单线程 Node的事件循环是单线程的。然而,这并不意味着在后台没有多个线程在工作。 Node会调用一些功能,例如文件系统(fs),它是用C ++实现的,而不是JavaScript实现。fs会使用工作线程来完成其功能。另外,Node使用libuv库,它使用工作线程池来实现某些功能。具体的线程数是与操作系统相关的。 如果你坚持使用JavaScript并创建了JavaScript模块,就再也不必考虑工作线程和libuv了。事实上:我们已经被告知,完全不必担心工作线程。对我这样一个在多线程环境中工作过的人来说,完全没有问题。 不过,如果你有兴趣为Node开发扩展插件,那么就需要非常熟悉libuv。libuv的介绍将会是一个好的开始。 想了解更多关于有趣且隐蔽的Node多线程世界,我建议参考Stack Overflow上关于“什么时候使用线程池?”的答案。