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

时间:2012-02-25 21:43:00

标签: node.js

我正在编写一个简单的请求处理程序来返回一对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();
}

4 个答案:

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

}