NodeJS: Confusion au sujet de “readdir” et de “stat” async

Dans la documentation, il montre deux versions de readdir et stat . readir/readdirSync et stat/statSync version synchrone et sync.

Comme readidir et stat sont asynchrones, je m’attendrais à ce qu’ils renvoient une promesse, mais lorsque vous essayez d’utiliser async/await wait, le script n’attend pas que readdir résolu et si j’utilise .then/.catch une erreur cannot read .then of undefined .

Tout ce que j’essaie de faire ici est de mapper les répertoires existant dans le répertoire dans lequel le script est exécuté vers la dirsOfCurrentDir .

Retourne l’erreur cannot read .then of undefined

 const fs = require('fs'); const directory = `${ __dirname }/${ process.argv[2] }`; const dirsOfCurrentDir = new Map(); fs.readdir(directory, (err, files) => { let path; if (err) return console.log(err); files.forEach(file => { path = directory + file; fs.stat(path, (err, stats) => { if (err) return console.log(err); dirsOfCurrentDir.set(file, directory); }); }); }).then(() => console.log('adasdasd')) console.log(dirsOfCurrentDir) 

Map {} retour Map {}

 const foo = async () => { await fs.readdir(directory, (err, files) => { let path; if (err) return console.log(err); files.forEach(file => { path = directory + file; fs.stat(path, (err, stats) => { if (err) return console.log(err); dirsOfCurrentDir.set(file, directory); }); }); }); }; foo() console.log(dirsOfCurrentDir) 

modifier

J’ai fini par readdirSync les versions synchrones de ces deux fonctions readdirSync et statSync . Bien que je me sente mieux d’utiliser les méthodes asynchrones ou promisify, je n’ai toujours pas compris comment faire fonctionner mon code correctement à l’aide de ces méthodes.

 const fs = require('fs'); const directory = `${ __dirname }/${ process.argv[2] }`; const dirsOfCurrentDir = new Map(); const dirContents = fs.readdirSync(directory); dirContents.forEach(file => { const path = directory + file; const stats = fs.statSync(path); if (stats.isDirectory()) dirsOfCurrentDir.set(file, path); }); console.log(dirsOfCurrentDir); // logs out the map with all properties set 

Parce que readidir et stat sont asynchrones, je m’attendrais à ce qu’ils me renvoient une promesse

Tout d’abord, assurez-vous de connaître la différence entre une fonction asynchrone et une fonction async . Une fonction déclarée async utilisant ce mot clé spécifique en Javascript, telle que:

 async function foo() { ... } 

renvoie toujours une promesse (selon la définition d’une fonction déclarée avec le mot clé async ).

Mais une fonction asynchrone telle que fs.readdir() peut ou non renvoyer une promesse, en fonction de sa conception interne. Dans ce cas particulier, l’implémentation initiale du module fs dans node.js utilise uniquement des rappels, pas des promesses (sa conception est antérieure à l’existence de promesses dans node.js). Ses fonctions sont asynchrones, mais ne sont pas déclarées comme async et utilisent donc des rappels réguliers, pas des promesses.

Donc, vous devez soit utiliser les callbacks, soit “promisify” l’interface pour la convertir en quelque chose qui retourne une promesse afin que vous puissiez l’utiliser en await .

Il existe une interface expérimentale dans node.js v10 qui offre des promesses intégrées pour le module fs.

 const fsp = require('fs').promises; fsp.readdir(...).then(...) 

Il existe de nombreuses options pour promettre des fonctions dans une version antérieure de node.js. Vous pouvez le faire fonction par fonction en utilisant util.promisify () :

 const promisify = require('util').promisify; const readdirP = promisify(fs.readdir); const statP = promisify(fs.stat); 

Comme je ne développe pas encore le noeud v10, j’utilise souvent la bibliothèque de promesses Bluebird et promisifie l’intégralité de la bibliothèque fs à la fois:

 const Promise = require('bluebird'); const fs = Promise.promisifyAll(require('fs')); fs.readdirAsync(...).then(...) 

Pour lister simplement les sous-répertoires d’un répertoire donné, vous pouvez faire ceci:

 const fs = require('fs'); const path = require('path'); const promisify = require('util').promisify; const readdirP = promisify(fs.readdir); const statP = promisify(fs.stat); const root = path.join(__dirname, process.argv[2]); // utility function for sequencing through an array asynchronously function sequence(arr, fn) { return arr.reduce((p, item) => { return p.then(() => { return fn(item); }); }, Promise.resolve()); } function listDirs(rootDir) { const dirsOfCurrentDir = new Map(); return readdirP(rootDir).then(files => { return sequence(files, f => { let fullPath = path.join(rootDir, f); return statP(fullPath).then(stats => { if (stats.isDirectory()) { dirsOfCurrentDir.set(f, rootDir) } }); }); }).then(() => { return dirsOfCurrentDir; }); } listDirs(root).then(m => { for (let [f, dir] of m) { console.log(f); } }); 

Voici une implémentation plus générale qui répertorie les fichiers et offre plusieurs options pour ce qu’il faut lister et comment présenter les résultats:

 const fs = require('fs'); const path = require('path'); const promisify = require('util').promisify; const readdirP = promisify(fs.readdir); const statP = promisify(fs.stat); const root = path.join(__dirname, process.argv[2]); // options takes the following: // recurse: true | false - set to true if you want to recurse into directories (default false) // includeDirs: true | false - set to true if you want directory names in the array of results // sort: true | false - set to true if you want filenames sorted in alpha order // results: can have any one of the following values // "arrayOfFilePaths" - return an array of full file path ssortingngs for files only (no directories included in results) // "arrayOfObjects" - return an array of objects {filename: "foo.html", rootdir: "//root/whatever", full: "//root/whatever/foo.html"} // results are breadth first // utility function for sequencing through an array asynchronously function sequence(arr, fn) { return arr.reduce((p, item) => { return p.then(() => { return fn(item); }); }, Promise.resolve()); } function listFiles(rootDir, opts = {}, results = []) { let options = Object.assign({recurse: false, results: "arrayOfFilePaths", includeDirs: false, sort: false}, opts); function runFiles(rootDir, options, results) { return readdirP(rootDir).then(files => { let localDirs = []; if (options.sort) { files.sort(); } return sequence(files, fname => { let fullPath = path.join(rootDir, fname); return statP(fullPath).then(stats => { // if directory, save it until after the files so the resulting array is breadth first if (stats.isDirectory()) { localDirs.push({name: fname, root: rootDir, full: fullPath, isDir: true}); } else { results.push({name: fname, root: rootDir, full: fullPath, isDir: false}); } }); }).then(() => { // now process directories if (options.recurse) { return sequence(localDirs, obj => { // add directory to results in place right before its files if (options.includeDirs) { results.push(obj); } return runFiles(obj.full, options, results); }); } else { // add directories to the results (after all files) if (options.includeDirs) { results.push(...localDirs); } } }); }); } return runFiles(rootDir, options, results).then(() => { // post process results based on options if (options.results === "arrayOfFilePaths") { return results.map(item => item.full); } else { return results; } }); } // get flat array of file paths, // recursing into directories, // each directory sorted separately listFiles(root, {recurse: true, results: "arrayOfFilePaths", sort: true, includeDirs: false}).then(list => { for (const f of list) { console.log(f); } }).catch(err => { console.log(err); }); 

Vous pouvez copier ce code dans un fichier et l’exécuter en passant . en tant qu’argument pour répertorier le répertoire du script ou tout nom de sous-répertoire que vous souhaitez répertorier.

Si vous vouliez moins d’options (par exemple, aucune récursion ou ordre de répertoire non préservé), ce code pourrait être réduit de manière significative et peut-être un peu plus rapide (exécuter certaines opérations asynchrones en parallèle).