使用fs.readFile在Node.js中读取和返回多个文件

我正在写一个简单的请求处理程序来返回一对CSS文件。 使用fs.readFileSync这很容易。 但是,我很难用asynchronous版本的readFile完成同样的任务。 以下是我的代码。 让我的response.write()方法调用拆分两个不同的callback似乎是有问题的。 有人能指出我做错了什么吗? 有趣的是,如果我把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(); } 

你所拥有的主要问题是response.end()被立即调用。 你只需要在文件完成response.write调用之后调用它。

最简单的方法是使用控制stream库。 pipe理多个asynchronouscallback通常很复杂。

https://github.com/joyent/node/wiki/modules#wiki-async-flow

我打算使用asynchronous库,因为这是我最了解的一个。

 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); } 

有一个简单的常见的解决scheme,让他们都有一个callback。 您可以将其放置在项目的任何位置以便在许多不同的情况下重用。

 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:顺便说一句,这样的请求,你最好caching在前端代理服务器,如nginx或清漆。 这永远不会改变。

你可以简单地依靠html5的承诺。 代码可以简单如下:

 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(); }); 

asynchronous是一个很棒的lib。 然而,这些事情的标准正朝着承诺处理多个asynchronous操作的方向发展。 实际上在ECMAScript6中, 这将成为库的标准部分 。 有几个实现包括JQuery的promise的库。 但是,对于节点,我喜欢用'q'

下面是使用promise的相同代码:一个注意事项..您可能需要将第一个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 }); }