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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9448336/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 15:14:18  来源:igfitidea点击:

Reading and returning multiple files in Node.js using fs.readFile

node.js

提问by hughesdan

I'm writing a simple request handler to return a pair of css files. Using fs.readFileSync this was easy. However, I'm having difficulty accomplishing the same task using the async version of readFile. Below is my code. Having my response.write() method calls split among two different callbacks seems to be problematic. Can someone point out what I've done wrong? Interestingly this code works if I put response.end() inside of the first else statement. However, that creates a problem in that the second css file does not get returned (because response.end() has already been fired).

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

回答by loganfsmyth

The primary issue with what you have is that response.end()gets called right away. You need to only call it after the files have done their response.writecalls.

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

The easiest way would be to use a control flow library. Managing multiple asynchronous callbacks is generally complicated.

最简单的方法是使用控制流库。管理多个异步回调通常很复杂。

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

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

I'm going to use the asynclibrary because it's the one I know best.

我将使用异步库,因为它是我最了解的库。

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

If you want to accomplish this without a library, or just want another way, this is how I would do it more directly. Basically you keep a countand call endonce both file reads have finished.

如果你想在没有库的情况下完成这个,或者只是想要另一种方式,这就是我更直接的方式。基本上countend一旦两个文件读取完成,您就会保持一个和调用。

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

回答by orabis

You can simply rely on html5 Promise. The code can be as simple as follows:

您可以简单地依赖 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();
});

回答by Alex Yaroshevich

There's a simple common solution to get them all with an one callback. You can place it anywhere in your project to reuse in many different cases.

有一个简单的通用解决方案可以通过一个回调来获取它们。您可以将其放置在项目中的任何位置,以便在许多不同的情况下重复使用。

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

    });
}

Just put inside it a bulk of files and it will returns to you each of them as a buffer. Simple example:

只需将大量文件放入其中,它就会将每个文件作为缓冲区返回给您。简单的例子:

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: Btw, requests like this you better to cache on front-end proxy server like nginx or varnish. It's never change.

题外话:顺便说一句,这样的请求最好缓存在前端代理服务器上,比如 nginx 或 varnish。它永远不会改变。

回答by Cheruvim

Async is an awesome lib. However the standard for these things is moving in the direction of promises for handling multiple asynchronous operations. In fact in ECMAScript6 this will be a standard part of the library. There are several libraries that implement promises including JQuery. However, for node, I like to use 'q'

Async 是一个很棒的库。然而,这些事情的标准正在朝着处理多个异步操作的承诺方向发展。事实上,在 ECMAScript6 中,这将是库的标准部分。有几个库实现了 Promise,包括 JQuery。但是,对于节点,我喜欢使用“q”

Here is the same code using promises: One note.. you might want to move the first writeHead call to coincide with the first successful read.

下面是使用 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
   });

}