说明:本人是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()读取文件。
总结:
无论你是否有需要执行一个耗时很长的程序与否,你都应该使用非阻塞模型,并且记住,正确使用回调和异步可以让代码的速度和稳定性都能得到提高。