2011-06-09 11 views
6

Estoy tratando de usar el módulo de nodejs express FS para iterar un directorio en mi aplicación, almacenar cada nombre de archivo en una matriz, que puedo pasar a mi vista expresa e iterar a través del lista, pero estoy luchando para hacerlo. Cuando hago un console.log dentro del bucle de la función files.forEach, su impresión del nombre de archivo muy bien, pero tan pronto como yo trato de hacer cualquier cosa, como:nodejs express fs iterando archivos en la matriz o el objeto que falla

var myfiles = []; 
var fs = require('fs'); 
fs.readdir('./myfiles/', function (err, files) { if (err) throw err; 
    files.forEach(function (file) { 
    myfiles.push(file); 
    }); 
}); 
console.log(myfiles); 

falla, simplemente registra un objeto vacío. Así que no estoy seguro de qué está pasando exactamente, creo que tiene que ver con las funciones de devolución de llamada, pero si alguien pudiera explicarme qué es lo que estoy haciendo mal y por qué no funciona (y cómo hacerlo funcionar), sería muy apreciado.

Respuesta

30

La matriz myfiles está vacía porque no se ha llamado a la devolución de llamada antes de llamar a console.log().

Tendrá que hacer algo como:

var fs = require('fs'); 
fs.readdir('./myfiles/',function(err,files){ 
    if(err) throw err; 
    files.forEach(function(file){ 
     // do something with each file HERE! 
    }); 
}); 
// because trying to do something with files here won't work because 
// the callback hasn't fired yet. 

Recuerde, todo lo que sucede en el nodo al mismo tiempo, en el sentido de que, a menos que usted está haciendo su procesamiento dentro de sus devoluciones de llamada, no se puede garantizar las funciones asíncronas se han completado todavía.

Una forma de evitar este problema para usted sería utilizar un EventEmitter:

var fs=require('fs'), 
    EventEmitter=require('events').EventEmitter, 
    filesEE=new EventEmitter(), 
    myfiles=[]; 

// this event will be called when all files have been added to myfiles 
filesEE.on('files_ready',function(){ 
    console.dir(myfiles); 
}); 

// read all files from current directory 
fs.readdir('.',function(err,files){ 
    if(err) throw err; 
    files.forEach(function(file){ 
    myfiles.push(file); 
    }); 
    filesEE.emit('files_ready'); // trigger files_ready event 
}); 
+3

+1 para profundizar y explicar cómo se supone que debes usar la devolución de llamada. –

+0

palabra, justo lo que necesitaba, ¡gracias! – thrice801

+0

Simplemente use la versión Sync de readdir en su lugar si necesita esperar a que termine: [fs.readdirSync] (http://nodejs.org/docs/v0.3.1/api/fs.html#fs.readdirSync) – Automatico

5

fs.readdir es asincrónico (como con muchas operaciones en node.js). Esto significa que la línea console.log se ejecutará antes de que readdir tenga la oportunidad de llamar a la función que se le haya pasado.

Necesitas sea:

Ponga la línea console.log dentro de la función de devolución de llamada dada a readdir, es decir:

fs.readdir('./myfiles/', function (err, files) { if (err) throw err; 
    files.forEach(function (file) { 
    myfiles.push(file); 
    }); 
    console.log(myfiles); 
}); 

O simplemente realizar alguna acción con cada archivo dentro de la forEach.

2

Creo que tiene que ver con las funciones de devolución de llamada,

Exactamente.

fs.readdir realiza una solicitud asincrónica al sistema de archivos para esa información, y llama a la devolución de llamada en algún momento posterior con los resultados.

Así que function (err, files) { ... } no se ejecuta inmediatamente, pero console.log(myfiles) lo hace.

En algún momento posterior, myfiles contendrá la información deseada.

Debe tener en cuenta que files ya es una matriz, por lo que no tiene sentido agregar manualmente cada elemento a otra matriz en blanco. Si la idea es reunir los resultados de varias llamadas, entonces use .concat; si solo desea obtener los datos una vez, puede simplemente asignar myfiles = files directamente.

En general, realmente debe leer en "Continuation-passing style".

3

Como han mencionado varios, está utilizando un método async, por lo que tiene una ruta de ejecución no determinista.

Sin embargo, hay una manera fácil de evitar esto. Simplemente use la versión Sync del método:

var myfiles = []; 
var fs = require('fs'); 

var arrayOfFiles = fs.readdirSync('./myfiles/'); 

//Yes, the following is not super-smart, but you might want to process the files. This is how: 
arrayOfFiles.forEach(function (file) { 
    myfiles.push(file); 
}); 
console.log(myfiles); 

Eso debería funcionar como desee. Sin embargo, el uso de declaraciones de sincronización no es bueno, por lo que no debes hacerlo a menos que sea de vital importancia para que se sincronice.

leer más aquí: fs.readdirSync

+0

↑ para la respuesta más simple. –

0

que enfrentan el mismo problema, y ​​basándose en las respuestas dadas en este post me he solucionado con promesas, que parecen ser de utilidad perfecta en esta situación:

router.get('/', (req, res) => { 
    var viewBag = {}; // It's just my little habit from .NET MVC ;) 

    var readFiles = new Promise((resolve, reject) => { 
    fs.readdir('./myfiles/',(err,files) => { 
     if(err) { 
     reject(err); 
     } else { 
     resolve(files); 
     } 
    }); 
    }); 

    // showcase just in case you will need to implement more async operations before route will response 
    var anotherPromise = new Promise((resolve, reject) => { 
    doAsyncStuff((err, anotherResult) => { 
     if(err) { 
     reject(err); 
     } else { 
     resolve(anotherResult); 
     } 
    }); 
    }); 

    Promise.all([readFiles, anotherPromise]).then((values) => { 
    viewBag.files = values[0]; 
    viewBag.otherStuff = values[1]; 
    console.log(viewBag.files); // logs e.g. [ 'file.txt' ] 
    res.render('your_view', viewBag); 
    }).catch((errors) => { 
    res.render('your_view',{errors:errors}); // you can use 'errors' property to render errors in view or implement different error handling schema 
    }); 
}); 

Nota: usted no tiene que empujar archivos encontrados en la nueva matriz debido a que ya obtiene una matriz de fs.readdir() 'c devolución de llamada. De acuerdo con el nodo docs:

La devolución de llamada recibe dos argumentos (err, archivos) donde archivos es una matriz de los nombres de los archivos en el directorio de exclusión '' y '..'.

Creo que esta es una solución muy elegante y práctica, y sobre todo, no requiere que traiga y maneje nuevos módulos a su secuencia de comandos.

Cuestiones relacionadas