Node.js学习笔记(3)——关于回调函数和函数的回调

说明:本人是node.js的初学者,尝试向别人解释这是怎么回事是自我学习的一个好方法。如果你发现有些地方并不是那么正确,欢迎提出来让我知道以便修正,共同进步,谢过^_^。       欢迎交流,本人微博:http://weibo.com/bitsea 

很多地方都涉及到函数的回调,在这里简单说一下什么是函数的回调。

回调函数就是回来再调用的函数。

基于js的单线程执行代码的风格,回调是必须的选择。也可以说是一种不得已而为之的选择吧,回调无疑增加了代码的复杂性,使其变得难读、难理解,难维护。但是,在解决实际问题的时候,回调又非常有效。下面举例说明:

我们要读取一个很大的文件,但是我们有不需要立刻获得读取的结果,并且,我们可不只有读取文件这个任务,我们还要执行其他的代码。这个时候,我们就需要回调函数过来帮忙。

1 var fs=require(“fs”);
2 fs.readFile(‘text.txt’,utf-8,function(err,result){
3     If(err) throw err;
4     //result就是读取出来的数据
5 });
6 console.log(‘下一项任务’);

这样一来,就可以把读文件那个任务放一放了,我们完成完我们的下一个任务的时候,闲得慌的时候再回来执行它不晚,有错误就抛出来。

这个方法在很多的node.js工程上面有大量的应用(就我接触的而言),复制一段项目里面的几句代码(有关查询数据库):

1 conn.query("select login,qq,phone,type from user where name='"+myphone+"'",function(err, rowss, fields) {
2 
3          if (err) {console.log(err);res.end();return;}
4 
5          if(rowss.length<1) {res.end("{{}}alert{}操作失败,你的手机没有登记过{{}}");return;}

 

这里的function就是回调函数。

 

我又在另一篇国外的人写的文章,他从客户端的角度阐述了为什么采取回调函数的解决方案是node的必然选择:我们知道,php为每一个客户端开辟一个新的线程用于服务新的请求,但是由于node的“先天残疾”,如果前一个请求需要花费5s,后一个请求就不得不等待5s!这是不能忍的,我在抢票回家过年好咩!!这个叫阻塞,前一个阻塞掉后一个。我们要把它变成一个非阻塞的,每个人都有公平的机会抢到回家的票。于是就用到回调函数。

文章中还打了一个很有意思的比喻,这里摘抄过来:

你在一个狭窄的道路上开车,前面有一个SB在停着打电话,很忙的样子(阻塞代码)使你不能到达目的地,这样你必须等这个SB打完电话把车启动起来才 能继续(有人可能想,用板砖干他丫的,但从程序角度来说,把他丫干死,前面少了一个司机,你要等警察来拖走或者自己先开走他的后再开自己的车,外加法律责 任,代价是很大滴,这叫破坏模型,比阻塞模型的代价还大)。

想像一下如果这条路上有紧急停车带,前面那SB司机可以变得不SB,先把车停在紧急停车带打电话。把路让出来让你先继续你的旅程。当那个不再SB的 司机打完电话之后也可以回到主干上来继续前行,还避免了可能碰到的板砖型程序员而导致血光之灾,皆大欢喜。这跟异步调用很像,在同一时间同一主干上跑多辆车。

非常有趣,isn’t it?

咳咳,好了言归正传。上代码(三段)说明问题!!

 

var http = require('http'); 

var url = require('url'); 

http.createServer(function (request, response) { 

    response.writeHead(200, {'Content-Type': 'text/plain'}); 

     if( url.parse(request.url).pathname == '/wait' ){ 

        var startTime = new Date().getTime(); 

        while (new Date().getTime() < startTime + 15000); 

        response.write('Thanks for waiting!'); 

    } else{ 

        response.write('Hello!'); 

    } 

     response.end(); 

}).listen(8080); 

 console.log('Server started');

 

 

 

代码读起来不难,创建了可一个服务器,监听8080端口。我们运行node这个文件之后,在浏览器里面输入localhost:8080/wait回车,代码开始起作用,十五秒之后蹦出来一句话。

这不能说明什么问题。但是,在你按下回车之后的15秒内,另一个客户需要访问这个这个服务器的时候,也需要等待你完成之后才轮到他。黄花菜都凉了!

 

下一段代码(包含两个文件):

block.js:

var startTime = new Date().getTime(); 

while (new Date().getTime() < startTime + 10000); 

  

 

main.js:

var http = require('http'); 

var url = require('url'); 

var cp = require('child_process'); 

function onRequest(request, response) { 

    var pathname = url.parse(request.url).pathname; 

    if( pathname == '/wait' ){ 

        cp.exec('node block.js', myCallback); 

    } 

    else{ 

        response.writeHead(200, {'Content-Type': 'text/plain'}); 

        response.write('Hello!\n'); 

        response.end(); 

    } 

    console.log('New connection'); 

    function myCallback(){ 

        response.writeHead(200, {'Content-Type': 'text/plain'}); 

        response.write('Thanks for waiting!\n'); 

        response.end(); 

    } 

} 

http.createServer(onRequest).listen(8080); 

console.log('Server started'); 

 

  

两段代码类似,这个多申请了一个cp子进程,用于在用户访问wait域名的时候调用block.js。在其他用户访问非wait域名的时候,服务器仍然能及时响应hello,这个时候道路还是通畅的,原因就是上一个sb还在紧急停车带打电话呢!这一切都归功于cp.exec('node block.js', myCallback);里面的回调函数:myCallback。.exec函数有两个参数,一个调用block.js,另外一个执行回调函数。

第三段代码(一个读取文件的程序):

var http = require('http'); 

var fileSystem = require('fs'); 

http.createServer(function (request, response) { 

    response.writeHead(200, {'Content-Type': 'text/plain'}); 

    var read_stream = fileSystem.createReadStream('myfile.txt'); 

    read_stream.on('data', writeCallback); 

    read_stream.on('close', closeCallback); 

    function writeCallback(data){ 

        response.write(data); 

    }  

    function closeCallback(){ 

        response.end(); 

    }  }).listen(8080);  

console.log('Server started'); 

  

这里使用了内置的文件操作模块,使用函数.createReadStream()读取文件。

总结:

无论你是否有需要执行一个耗时很长的程序与否,你都应该使用非阻塞模型,并且记住,正确使用回调和异步可以让代码的速度和稳定性都能得到提高。

 

posted @ 2014-09-04 19:29  周县长  阅读(3753)  评论(0编辑  收藏  举报