function start(route, handle) {
function onRequest(request, response) {
var pathname = url.parse(request.url).pathname;
console.log("Request for " + pathname + " received.");
response.writeHead(200, {"Content-Type": "text/plain"});
var content = route(handle, pathname)
response.write(content);
response.end();
}
http.createServer(onRequest).listen(8888);
console.log("Server has started.");
}
exports.start = start;
如果我们运行重构后的应用,一切都会工作的很好:请求:8888/start,浏览器会输出“Hello Start”,请求:8888/upload会输出“Hello Upload”,而请求:8888/foo 会输出“404 Not found”。
好,那么问题在哪里呢?简单的说就是: 当未来有请求处理程序需要进行非阻塞的操作的时候,我们的应用就“挂”了。
没理解?没关系,下面就来详细解释下。
阻塞与非阻塞
正如此前所提到的,当在请求处理程序中包括非阻塞操作时就会出问题。但是,在说这之前,我们先来看看什么是阻塞操作。
我不想去解释“阻塞”和“非阻塞”的具体含义,我们直接来看,当在请求处理程序中加入阻塞操作时会发生什么。
这里,我们来修改下start请求处理程序,我们让它等待10秒以后再返回“Hello Start”。因为,JavaScript中没有类似sleep()这样的操作,所以这里只能够来点小Hack来模拟实现。
让我们将requestHandlers.js修改成如下形式:
复制代码 代码如下:
function start() {
console.log("Request handler 'start' was called.");
function sleep(milliSeconds) {
var startTime = new Date().getTime();
while (new Date().getTime() < startTime + milliSeconds);
}
sleep(10000);
return "Hello Start";
}
function upload() {
console.log("Request handler 'upload' was called.");
return "Hello Upload";
}
exports.start = start;
exports.upload = upload;
上述代码中,当函数start()被调用的时候,Node.js会先等待10秒,之后才会返回“Hello Start”。当调用upload()的时候,会和此前一样立即返回。
(当然了,这里只是模拟休眠10秒,实际场景中,这样的阻塞操作有很多,比方说一些长时间的计算操作等。)
接下来就让我们来看看,我们的改动带来了哪些变化。
如往常一样,我们先要重启下服务器。为了看到效果,我们要进行一些相对复杂的操作(跟着我一起做): 首先,打开两个浏览器窗口或者标签页。在第一个浏览器窗口的地址栏中输入:8888/start, 但是先不要打开它!
在第二个浏览器窗口的地址栏中输入:8888/upload, 同样的,先不要打开它!
接下来,做如下操作:在第一个窗口中(“/start”)按下回车,然后快速切换到第二个窗口中(“/upload”)按下回车。
注意,发生了什么: /start URL加载花了10秒,这和我们预期的一样。但是,/upload URL居然也花了10秒,而它在对应的请求处理程序中并没有类似于sleep()这样的操作!
这到底是为什么呢?原因就是start()包含了阻塞操作。形象的说就是“它阻塞了所有其他的处理工作”。
这显然是个问题,因为Node一向是这样来标榜自己的:“在node中除了代码,所有一切都是并行执行的”。
这句话的意思是说,Node.js可以在不新增额外线程的情况下,依然可以对任务进行并行处理 —— Node.js是单线程的。它通过事件轮询(event loop)来实现并行操作,对此,我们应该要充分利用这一点 —— 尽可能的避免阻塞操作,取而代之,多使用非阻塞操作。
然而,要用非阻塞操作,我们需要使用回调,通过将函数作为参数传递给其他需要花时间做处理的函数(比方说,休眠10秒,或者查询数据库,又或者是进行大量的计算)。
对于Node.js来说,它是这样处理的:“嘿,probablyExpensiveFunction()(译者注:这里指的就是需要花时间处理的函数),你继续处理你的事情,我(Node.js线程)先不等你了,我继续去处理你后面的代码,请你提供一个callbackFunction(),等你处理完之后我会去调用该回调函数的,谢谢!”
(如果想要了解更多关于事件轮询细节,可以阅读Mixu的博文——理解node.js的事件轮询。)
接下来,我们会介绍一种错误的使用非阻塞操作的方式。
和上次一样,我们通过修改我们的应用来暴露问题。
这次我们还是拿start请求处理程序来“开刀”。将其修改成如下形式:
复制代码 代码如下:
var exec = require("child_process").exec;
function start() {
console.log("Request handler 'start' was called.");
var content = "empty";
exec("ls -lah", function (error, stdout, stderr) {
content = stdout;
});
return content;
}
function upload() {
console.log("Request handler 'upload' was called.");
return "Hello Upload";
}