Чтение и возвращение нескольких файлов в Node.js с помощью fs.readFile - программирование
Подтвердить что ты не робот

Чтение и возвращение нескольких файлов в Node.js с помощью fs.readFile

Я пишу простой обработчик запросов, чтобы вернуть пару css файлов. Использование fs.readFileSync было легко. Тем не менее, мне трудно выполнить ту же задачу, используя асинхронную версию readFile. Ниже мой код. Наличие вызовов метода response.write(), разделенных между двумя разными обратными вызовами, кажется проблематичным. Может кто-нибудь указать, что я сделал неправильно? Интересно, что этот код работает, если я положил response.end() внутри оператора else 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();
}
4b9b3361

Ответ 1

Основная проблема с тем, что у вас есть, - это то, что 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);
}

Ответ 2

Есть простое общее решение, чтобы получить их все с одним обратным вызовом. Вы можете поместить его в любом месте вашего проекта для повторного использования во многих разных случаях.

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: Btw, запросы, подобные этому, вам лучше кэшировать на переднем прокси-сервере, таком как nginx или лак. Он никогда не меняется.

Ответ 3

Вы можете просто полагаться на 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();
});

Ответ 4

Async - это потрясающая библиотека. Однако стандарт для этих вещей движется в направлении promises для обработки нескольких асинхронных операций. Фактически в ECMAScript6 это будет стандартная часть библиотеки. Существует несколько библиотек, которые реализуют promises, включая JQuery. Однако для node мне нравится использовать '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
   });

}