node.js异步编程

        ajax的盛行,使javascript成为前端开发人员的宠儿.现今js已经可以通过nodejs在服务器运行.本文将通过对一段代码的逐渐改进来展示如何让程序员使用javascript快乐得进行非阻塞异步编程.nodejs和mongodb的具体安装和使用方法请参考文章最后的参考文献列表.

Hello,Node !

        nodejs是编程就绪的,任何功能都要通过编程实现.这和Tomcat,ApacheHTTP等运行就绪服务不同,后者安装后可以直接运行就可以通过浏览器访问.而nodejs起码要有如下代码.

var http = require('http');

http.createServer(function(req,resp){
  resp.end('Hello , Node !');
}).listen(8124);

console.log('server startup');

将这些代码保存为hellonode.js然后在命令行执行node hellonode.js.在浏览器通过http://localhost:8124/即可访问该服务,浏览器窗口会显示"Hello , Node !"字样.这段代码的意义很容易理解,开头的require相当于java中的import将http模块引入,后面使用http模块的API构建http服务.javascript回调用来处理收到的请求,并返回字符串给浏览器.

一个稍微复杂的例子

        现实环境中,常规得处理http请求的过程大概是这样的.首先,服务器收到请求并从中解析出请求参数,然后使用这些参数进行数据库查询,最后将得到的结果放入模板生成视图响应给用户.伪代码大概如下这个样子.

var http = require('http');
var queryString = require('queryString');
var url = require('url');

var db = {};//初始化数据库

http.createServer(function (req, resp) {
  //1.解析请求参数
  var params = queryString.parse(url.parse(req.url).query);
  var template = 'result = _';
  //2.使用参数查询数据
  db.query(params, function (err, result) {
    //3.在回调中使用查询结果
    if (err) throw err;
    var view = template.replace(/_/, result);
    //4.返回视图,响应给客户端
    resp.end(view);
  });
}).listen(8124);

console.log('server startup');

变得更复杂

可以看到代码(2)部分由于数据库查询在node中是无阻塞的,所以必须将所有后续处理查询结果的代码作为回调来使用,上面代码的处理过程比较简单,但是如果放入真实的应用中,不一定如此,比如模板要根据请求参数从本地文件系统加载.代码就会变为如下样子.

var http = require('http');
var queryString = require('queryString');
var url = require('url');
var fs = require('fs');

var db = {};//初始化数据库

http.createServer(function (req, resp) {
  //解析请求参数
  var params = queryString.parse(url.parse(req.url).query);
  //使用参数查询数据
  db.query(params, function (err, result) {
    //在回调中使用查询结果
    if (err) throw err;
    fs.readFile(params.path, function (err, template) {
      if (err) throw err;
      //填充模板
      var view = template.replace(/_/, result);
      //返回
      resp.end(view);
    })
  });
  resp.end('Hello , Node !');
}).listen(8124);

console.log('server startup');

深层嵌套问题

 可以看到由于异步编程所以难免产生了又一层的嵌套回调.可以想想当处理过程更复杂的时候,会产生像如下的深层嵌套问题.

setp1(function (err, result1) {
  setp2(result1, function (err, result2) {
    setp3(result2, function (err, result3) {
      step4(result3, function (err, result4) {
        //更多的嵌套回调
      })
    })
  })
});

事件模型和Promise 

这样代码的编写阅读都非常困难.好在现在有许多编程模型可以解决这个问题,例如事件模型(发布订阅).

//注册事件监听器
server.on('connection', function (stream) {
  console.log('someone connected!');
});

//当如下代码被调用时上面的注册回调将被调用
server.emit('connection')//发布事件

 不过这样有一个问题,可以想象如果将上面的深层嵌套用这种方式编写,需要发布四次事件和注册四次监听器.本来一段内聚的代码会变得相当松散,依然没有根本解决读写理解困难的问题.下面介绍Promise模型.大概可以这样理解,每当异步处理执行时,总是返回一个Promise,该Promise保证在处理完成或发成错误时才调用后续的方法.Promise通常有这样的API.

promise.then(success,failure)

success,failure分别表示promise承诺的处理成功或失败时的回调函数.

上面的深层嵌套用该模型重写即可变为如下形式

step1().then(step2).then(step3).then(step3)

 这样就容易理解多了.要使用Promise模型,需要想node中引入支持库,我们使用'q'这个模块.可以使用npm install q进行安装.'q'这个模块提供了很多工具方法来将普通的回调转换为Promise.使用'q'改写最上面的请求处理,伪代码如下:

var http = require('http');
var queryString = require('queryString');
var url = require('url');
var fs = require('fs');
var q = require('q');

var db = {};//初始化数据库

http.createServer(function (req, resp) {
  //解析请求参数
  var params = queryString.parse(url.parse(req.url).query);
  //q.all将数组中的promise合并
  q.all([
          //q.ninvoke将原本的回调式调用转化为返回Promise对象
          q.ninvoke(db, 'query', params),
          q.ninvoke(fs, 'readFile', params.path)
        ])
    //当上述两个promise都处理完成时继续调用
    .then(function (result) {
            //返回结果为一个数组,数组中分别存放上面被组合的promise的结果
            var model = result[0];
            var template = result[1];
            var view = template.replace(/_/, model);
            resp.end(view);
          });
}).listen(8124);

console.log('server startup');

 这样看起来就容易理解多了,但问题是内部细节很难懂.好在有'q'这样的库帮帮我们处理.

使用CoffeeScript让代码变得更紧凑

javascript是一门古老的语言,不免语法冗余了点儿,好在现在有coffeescript这类的简单语言可以编译为javascript来运行.使用coffee来改写上面代码,就更加紧凑了.

http.createServer (req, resp)->
  param = queryString.parse(url.parse(req.url).query)
  q.all([ q.ninvoke(db, 'query', params),
          q.ninvoke(fs, 'readFile', params.path)])
    .then((result)->
           model = result[0]
           template = result[1]
           view = template.replace(/_/, modle)
           resp.end view)
console.log 'server startup'

参考文献

Node.js究竟是什么?

Node.js API

node/q 项目

CoffeeScript

javascript异步编程的4种方法

JavaScript异步编程的Promise模式

猜你喜欢

转载自blog.csdn.net/u012631045/article/details/16898727