`

Node.js的Reactor模式 与异步编程

 
阅读更多

http://www.jdon.com/46372

在Ruby on Rails和NodeJS开发者之间曾经引起宗教类的口水战:顺序编程风格 Vs 基于事件编程。目前大部分Web应用包括Ruby on Rails, Java Spring, Django都是使用顺序编程风格。顺序编程是非常简单和可读的,大部分开发者都是以顺序方式思考,喜欢将一个应用逻辑划分为顺序的时序步骤。顺序编程通常会导致堵塞I/O,因为线程是遵循先来后到的多任务方式,而不是一种协作式的多任务方式,而非堵塞I/O能够带来更好地扩展性和性能。

这是一篇有关Node.js的非堵塞reactive编程案例,文章以一个简单的根据id查询的RESTful案例为例,从堵塞IO谈到回调函数的使用,然后谈论如何在代码可扩展性和可读性之间取得平衡,引入Promise与Fibers编程。大意翻译如下:

下面代码是根据facebook的id查询用户事件数据的实现,顺序编程代码风格是如下(JS伪代码):

function getUserEvents(request,response){
  var facebook_id = request.param('facebook_id');
  try{
      var user = db.users.findOne({fb_id:facebook_id});
      var events = db.events.find({user_id:user.id});
      response.write(events);
  }catch(err){
      response.status(500).send(err);
  }
}



顺序编程通常会导致堵塞I/O,因为线程是遵循先来后到的多任务方式,而不是一种协作式的多任务方式。

下面我们逐步引入异步来提升这段代码。

基于回调的解决方案
为了解决堵塞的I/O问题,代码可以被划分为三个部分:
1. 在进行网络或IO调用前的过程处理。
2.网络或IO调用。
3.从网络或IO调用中得到返回数据的处理。

上面这三步都是分离执行的,然后它们在NodeJS的事件循环中被触发执行。

function getUserEvents(request,response){

  var returnEvents = function(err,events){
          if (err) respone.status(500).send(err);;
          response.write(events);
      });  

  var givenUserFindAndReturnEvents = function(err,user){
      if (err) respone.status(500).send(err);;
      db.events.find({user_id:user.id},returnEvents);    
  };

  var findUserAndReturnEvents = function(){
      var facebook_id = request.param('facebook_id');
      db.users.findOne({fb_id:facebook_id}, givenUserFindAndReturnEvents);
  }

  findUserAndReturnEvents();
}


注意到请求和响应并没有被传递到子函数中,但是子函数可以访问request 和response,因为子函数是一个javascript 闭包. (当调用findUserAndReturnEvents函数时,相当于findUserAndReturnEvents -> givenUserFindAndReturnEvents ->returnEvents 这样一个流式调用)

这三个子函数都是异步执行的, givenUserFindAndReturnEvents 和 returnEvents是在findUserAndReturnEvents回调函数中触发执行的。

这三个子函数执行方式可以使用嵌套的lambda函数风格或findUserAndReturnEvents. givenUserFindAndReturnEvents.returnEvents 这种in-line方式。

这段代码有几个特点:
1.代码分离成网络调用前与网络调用后两个阶段
2.子函数调用者为了完成子函数任务必须传递一个回调函数。
3.顺序逻辑被表达成异步了
4.异步代码更加可扩展伸缩,但是也许增加响应延迟。
5.但是回调会引起可读性的问题:回调地狱 callback hell.
6.跟踪执行流程是困难的,因此称为意大利面条代码spaghetti-code.
7.非堵塞的API可能会限制妨碍你组织你的代码,有侵入性。
8.函数是有层次的。

基于Promise的解决方案
为了解决回调地狱,我们使用代码结构库如q promise. Promise库提供一些标准的代码风格和结构,使得代码更加易读。

var loadEventsForUser = function(err,user){
  return db.events.find({user_id:user.id});  
};

var findUser = function(){
  var facebook_id = request.param('facebook_id');
  return db.users.findOne({fb_id:facebook_id});
}

function getUserEvents(request,response){
  var success = function(events){
          response.write(events);
  };

  var error = function(err){
      response.status(500).send(err);
  };

  findUser()
  .then(loadEventsForUser)
  .then(success)
  .fail(error);
}


关键是最后一行的写法。
代码已经被划分为小的独立的函数,它们被以链条方式链接在一起,使用的是.then 和 .fail 函数. 另外一个重要特性是处理exception, 上面代码能够观察到错误然后调用相应的回调函数,错误处理被单独隔离了。

重要特点:
1.函数是扁平的,比如findUser能独立于loadEventsForUser函数调用。
2.将顺序编程代码切分成独立的可重用的函数并不总是很容易。
3.函数能够使用在其他流程被其他组件复用。
4.相比回调函数有更好的可读性。
5.比顺序编程有着更好地出错处理。

当然,我们还能更好地结合顺序编程可读优点和非堵塞可扩展的优点。待续..

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics