Node.JS
Generator与Fiber比较
ES6的generators 和node-fibers都能够用于在等待一些I/O时暂停一个协程,而不用堵塞整个主程序。这意味着你能在代码中等待I/O返回结果,而且依然拥有单线程的好处,从而获得非堵塞I/O模型的好处。
这两个主要的区别是语法是如何显式的,这其实体现了典型的安全和灵活之间的抉择。
Generator是安全但明显得烦人
在下面代码中使用generators,将一个generator函数传递入回调函数,然后暂停这个函数执行,当回调返回时继续执行。
下面是基于gen-run编写的服务器端代码:
http.createServer(function onRequest(req, res) {
run(handleRequest(req), function onResponseBody(err, body) {
if (err) {
res.statusCode = 500;
res.setHeader("Content-Type", "text/plain");
res.end(err.stack);
return;
}
res.statusCode = 200;
res.setHeader("Content-Type", "application/json");
res.setHeader("Content-Length", body.length);
res.end(body);
});
}).listen(3000);
console.log("Server running at http:
下面写个Handler直接返回结果:
module.exports = function (req) {
return {
method: req.method,
url: req.url,
date: (new Date).toString()
};
};
下面继续使用generator编写我们的业务逻辑,我们给每个结果加上请求数量:
var requestCount = 0;
function* handleRequest(req) {
requestCount++;
var result = query(req);
result.requestCount = requestCount;
return new Buffer(JSON.stringify(result) + "\n");
}
现在你可以如你所愿多个并发请求调用,每个都会返回一个正确的请求数量 requestCount ,这是因为JS的run-to-finish语义. 即使在generator函数体内, 任意的函数调用都不会搞乱你的代码。这体现了generator的安全优点。
generator缺点是不够灵活,如果我们加入一段慢的IO操作:
module.exports = query;
function query(req, callback) {
// If the callback isn't passed in, return a continuable.
if (!callback) return query.bind(this, req);
// 使用计时器模拟 I/O 操作
setTimeout(function () {
callback(null, {
method: req.method,
url: req.url,
date: (new Date).toString()
});
}, 100);
}
现在查询功能返回的是一个continuable或者可能是一个回调函数,而我们的处理器功能是下面这个样子:
var requestCount = 0;
function* handleRequest(req) {
requestCount++;
var result = yield query(req);
result.requestCount = requestCount; // Uh-Oh!
return new Buffer(JSON.stringify(result) + "\n");
}
我们的requestCount变量是处于一个竞争的危险状态。如果第二个请求进来,同时我们还在等待第一个请求的查询返回?他们彼此碰撞。
下面我们改造它,重写query查询作为一个 generator ,制造更多的function*和yield*:
module.exports = function* (req) {
// 使用sleep模拟一个 I/O
yield* sleep(100);
// 然后返回结果
return {
method: req.method,
url: req.url,
date: (new Date).toString()
}
};
function* sleep(ms) {
yield function (callback) {
setTimeout(callback, ms);
};
}
这样我们可以使用委派的 yield*来替代原来直接yield:
var requestCount = 0;
function* handleRequest(req) {
requestCount++;
var result = yield* query(req);
result.requestCount = requestCount; // Uh-Oh!
return new Buffer(JSON.stringify(result) + "\n");
}
generators能够让I/O不会堵塞整个处理流程,但是这可能导致对你代码有侵入式改变。非常类似回调地狱一样。
Fibers灵活不安全
Fiber服务器端代码有点不同于gen-run:
http.createServer(function onRequest(req, res) {
Fiber(function () {
var body;
try {
body = handleRequest(req);
}
catch (err) {
res.statusCode = 500;
res.setHeader("Content-Type", "text/plain");
res.end(err.stack);
return;
}
res.statusCode = 200;
res.setHeader("Content-Type", "application/json");
res.setHeader("Content-Length", body.length);
res.end(body);
}).run();
}).listen(3000);
console.log("Server running at http:
fiber不需要yield or yield*等侵入式特别代码形式。
Handler处理器代码:
var requestCount = 0;
function handleRequest(req) {
requestCount++;
var result = query(req);
result.requestCount = requestCount;
return new Buffer(JSON.stringify(result) + "\n");
}
这里不会发生竞争,因为没有人可以暂停Fiber。
现在加入改变,如果引入了慢的IO操作,看看代码是否能易于改变:
module.exports = function (req) {
// 使用sleep模拟 I/O
sleep(100);
// 返回结果
return {
method: req.method,
url: req.url,
date: (new Date).toString()
}
};
var Fiber = require('fibers');
function sleep(ms) {
var fiber = Fiber.current;
setTimeout(function() {
fiber.run();
}, ms);
Fiber.yield();
}
当服务器进入高并发负载时,这时requestCounts结果将不会很精确,几个小时痛苦的调试后发现的问题是,在handleRequest中不会改变的查询功能竟然会发生了行为变化,它暂停了fiber,让其他并发请求同时竞争访问共享的requestCount 变量。
在Node.js中使用Javascript Generators
基于Fibers开发Node.js的ExpressJS Restful服务