我正在编写一个简单的请求处理程序来返回一对css文件。使用fs.readFileSync这很容易。但是,我很难使用异步版本的readFile完成相同的任务。以下是我的代码。让我的response.write()方法调用分成两个不同的回调似乎是有问题的。有人能指出我做错了什么吗?有趣的是,如果我将response.end()置于第一个else语句中,则此代码可以正常工作。但是,这会产生一个问题,即第二个css文件没有返回(因为response.end()已被触发)。
function css(response) {
response.writeHead(200, {"Content-Type": "text/css"});
fs.readFile('css/bootstrap.css', function(error, content){
if(error){
console.log(error);
}
else{
response.write(content);
}
});
fs.readFile('css/bootstrap-responsive.css', function(error, content){
if(error){
console.log(error);
}
else{
response.write(content)
}
});
response.end();
}
答案 0 :(得分:31)
您拥有的主要问题是response.end()
会立即被调用。您只需在文件完成response.write
调用后调用它。
最简单的方法是使用控制流库。管理多个异步回调通常很复杂。
https://github.com/joyent/node/wiki/modules#wiki-async-flow
我将使用async库,因为它是我最熟悉的库。
var fs = require('fs');
var async = require('async');
function css(response) {
response.writeHead(200, {"Content-Type": "text/css"});
async.eachSeries(
// Pass items to iterate over
['css/bootstrap.css', 'css/bootstrap-responsive.css'],
// Pass iterator function that is called for each item
function(filename, cb) {
fs.readFile(filename, function(err, content) {
if (!err) {
response.write(content);
}
// Calling cb makes it go to the next item.
cb(err);
});
},
// Final callback after each item has been iterated over.
function(err) {
response.end()
}
);
}
如果你想在没有图书馆的情况下完成这项任务,或者只是想要其他方式,我就会更直接地做到这一点。基本上,您保留count
并在文件读取完成后调用end
。
function css(response) {
response.writeHead(200, {"Content-Type": "text/css"});
var count = 0;
var handler = function(error, content){
count++;
if (error){
console.log(error);
}
else{
response.write(content);
}
if (count == 2) {
response.end();
}
}
fs.readFile('css/bootstrap.css', handler);
fs.readFile('css/bootstrap-responsive.css', handler);
}
答案 1 :(得分:4)
你可以简单地依赖html5 Promise。代码可以简单如下:
var promises= ['file1.css', 'file2.css'].map(function(_path){
return new Promise(function(_path, resolve, reject){
fs.readFile(_path, 'utf8', function(err, data){
if(err){
console.log(err);
resolve(""); //following the same code flow
}else{
resolve(data);
}
});
}.bind(this, _path));
});
Promise.all(promises).then(function(results){
//Put your callback logic here
response.writeHead(200, {"Content-Type": "text/css"});
results.forEach(function(content){response.write(content)});
response.end();
});
答案 2 :(得分:1)
有一个简单的通用解决方案,可以通过一个回调来获取它们。 您可以将它放在项目的任何位置,以便在许多不同的情况下重复使用。
var FS = require('fs');
/**
* Abstract helper to asyncly read a bulk of files
* Note that `cb` will receive an array of errors for each file as an array of files data
* Keys in resulting arrays will be the same as in `paths`
*
* @param {Array} paths - file paths array
* @param {Function} cb
* @param {Array} errors - a list of file reading error
* @param {Array} data - a list of file content data
*/
function FS_readFiles (paths, cb) {
var result = [], errors = [], l = paths.length;
paths.forEach(function (path, k) {
FS.readFile(path, function (err, data) {
// decrease waiting files
--l;
// just skip non-npm packages and decrease valid files count
err && (errors[k] = err);
!err && (result[k] = data);
// invoke cb if all read
!l && cb (errors.length? errors : undef, result);
});
});
}
只需将大量文件放入其中,它将作为缓冲区返回给您。 简单的例子:
var cssFiles = [
'css/bootstrap.css',
'css/bootstrap-responsive.css'
];
function css(response) {
FS_readFiles(cssFiles, function (errors, data) {
response.writeHead(200, {"Content-Type": "text/css"});
data.forEach(function (v) {
response.write(v);
});
response.end();
});
}
Offtopic:顺便说一下,像这样的请求你最好在前端代理服务器上缓存,比如nginx或varnish。它永远不会改变。
答案 3 :(得分:-1)
异步是一个很棒的库。然而,这些事情的标准正在朝着处理多个异步操作的承诺的方向发展。实际上在ECMAScript6 this will be a standard part of the library中。有几个库实现了承诺,包括JQuery。但是,对于节点,我喜欢使用'q'
以下是使用promises的相同代码:一个注意事项..您可能希望移动第一个writeHead调用以与第一次成功读取重合。
var Q = require('q');
function css(response) {
response.writeHead(200, {"Content-Type": "text/css"});
var defer = Q.defer();
fs.readFile('css/bootstrap.css', function(error, content){
if(error){
defer.reject(error)
}
else{
response.write(content);
defer.resolve();
}
});
defer.promise.then(function() { //this gets executed when the first read succeeds and is written
var secondDefer = Q.defer();
fs.readFile('css/bootstrap-responsive.css', function(error, content){
if(error){
secondDefer.reject(error);
}
else{
response.write(content);
secondDefer.resolve();
}
});
return secondDefer.promise;
},
function(error) { //this gets called when the first read fails
console.log(error);
//other error handling
}).
done(function() {
response.end();
},
function(error) { //this is the error handler for the second read fails
console.log(error);
response.end(); //gotta call end anyway
});
}