2012-02-25 10 views
16

Escribo un controlador de solicitud simple para devolver un par de archivos css. Usar fs.readFileSync esto fue fácil. Sin embargo, tengo dificultades para realizar la misma tarea utilizando la versión asincrónica de readFile. A continuación está mi código. Tener mis llamadas al método response.write() divididas entre dos devoluciones de llamada diferentes parece ser problemático. ¿Puede alguien señalar lo que he hecho mal? Curiosamente, este código funciona si pongo response.end() dentro de la primera declaración else. Sin embargo, eso crea un problema porque el segundo archivo CSS no se devuelve (porque response.end() ya se ha activado).Lectura y devolución de archivos múltiples en Node.js usando fs.readFile

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

¿Está intentando agregar los dos archivos CSS en una respuesta? – loganfsmyth

+0

Sí, eso es correcto. – hughesdan

+0

¿Hay alguna razón por la que debe leer los archivos cada vez que alguien realiza una solicitud? ¿Esperas que cambien a menudo?De lo contrario, solo léalas en la memoria cuando se inicie el servidor y vuelva cuando sea necesario. Funciona más rápido. – Evgeny

Respuesta

26

El problema principal con lo que tiene es que se llama response.end() de inmediato. Solo tiene que llamar después de que los archivos hayan realizado sus llamadas response.write.

La manera más fácil sería utilizar una biblioteca de flujo de control. Gestionar múltiples devoluciones de llamada asincrónicas generalmente es complicado.

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

voy a utilizar la biblioteca async porque es la que mejor conozco.

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

Si quiere lograr esto sin una biblioteca, o simplemente quiere otra forma, así es como lo haría de manera más directa. Básicamente, mantenga un count y llame al end una vez que ambas lecturas de archivos hayan finalizado.

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

Gracias. ¡Esa fue una respuesta muy informativa! – hughesdan

+0

¿Algún lugar en particular al que llamarías función css? – Winnemucca

+0

Desde dentro de la devolución de llamada a https://nodejs.org/api/http.html#http_http_createserver_requestlistener o cualquiera de los paquetes del servidor API de Node. – loganfsmyth

1

Hay una solución común simple para obtenerlos todos con una devolución de llamada. Puede colocarlo en cualquier lugar de su proyecto para volver a utilizarlo en muchos casos diferentes.

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

    }); 
} 

Sólo hay que poner en su interior una mayor parte de los archivos y se vuelve a que cada uno de ellos como un amortiguador. ejemplo simple:

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: Por cierto, las solicitudes de este tipo es mejor para almacenar en caché en el servidor proxy front-end como nginx o barniz. Nunca es cambio.

-1

Async es una lib asombrosa. Sin embargo, el estándar para estas cosas se está moviendo en la dirección de promesas para manejar operaciones asincrónicas múltiples. De hecho, en ECMAScript6 this will be a standard part of the library. Hay varias bibliotecas que implementan promesas, incluida JQuery. Sin embargo, para el nodo, me gusta usar 'q'

aquí es el mismo código con promesas: Una nota .. Es posible que desee mover la primera llamada writeHead para coincidir con la primera lectura con éxito.

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

} 
0

Simplemente puede confiar en html5 Promise. El código puede ser tan simple como sigue:

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