Je travaille sur la méthode de publication côté serveur pour récupérer tous les fichiers dans le répertoire demandé (non récursif), et ci-dessous se trouve mon code.
J'ai des difficultés à renvoyer la réponse ( res.json (pathContent);
) avec le pathContent
mis à jour sans utiliser le setTimeout
.
Je comprends que c'est en raison du comportement asynchrone des méthodes de système de fichiers utilisées ( readdir
et stat
) et doivent utiliser une sorte de technique de rappel, asynchrone ou promesse.
J'ai essayé d'utiliser async.waterfall
avec le corps entier de readdir
comme une fonction et le res.json (pathContent)
comme le autre, mais il n'a pas envoyé le tableau mis à jour côté client.
Je sais qu'il y a eu des milliers de questions concernant cette opération asynchrone mais je n'ai pas pu trouver comment résoudre mon cas après avoir lu le nombre de posts.
Tout commentaire serait apprécié. Merci.
const express = require('express'); const bodyParser = require('body-parser'); const fs = require('fs'); const path = require('path'); var pathName = ''; const pathContent = []; app.post('/api/files', (req, res) => { const newPath = req.body.path; fs.readdir(newPath, (err, files) => { if (err) { res.status(422).json({ message: `${err}` }); return; } // set the pathName and empty pathContent pathName = newPath; pathContent.length = 0; // iterate each file const absPath = path.resolve(pathName); files.forEach(file => { // get file info and store in pathContent fs.stat(absPath + '/' + file, (err, stats) => { if (err) { console.log(`${err}`); return; } if (stats.isFile()) { pathContent.push({ path: pathName, name: file.substring(0, file.lastIndexOf('.')), type: file.substring(file.lastIndexOf('.') + 1).concat(' File'), }) } else if (stats.isDirectory()) { pathContent.push({ path: pathName, name: file, type: 'Directory', }); } }); }); }); setTimeout(() => { res.json(pathContent); }, 100); });
4 Réponses :
Le moyen le plus simple et le plus propre serait d'utiliser await
/ async
, de cette façon vous pouvez utiliser les promesses et le code ressemblera presque à du code synchrone.
Vous avez donc besoin d'une version promis de readdir
et stat
qui peut être créée par le promisify
de la bibliothèque principale utils
.
const { promisify } = require('util') const readdir = promisify(require('fs').readdir) const stat = promisify(require('fs').stat) async function getPathContent(newPath) { // move pathContent otherwise can have conflicts with concurrent requests const pathContent = []; let files = await readdir(newPath) let pathName = newPath; // pathContent.length = 0; // not needed anymore because pathContent is new for each request const absPath = path.resolve(pathName); // iterate each file // replace forEach with (for ... of) because this makes it easier // to work with "async" // otherwise you would need to use files.map and Promise.all for (let file of files) { // get file info and store in pathContent try { let stats = await stat(absPath + '/' + file) if (stats.isFile()) { pathContent.push({ path: pathName, name: file.substring(0, file.lastIndexOf('.')), type: file.substring(file.lastIndexOf('.') + 1).concat(' File'), }) } else if (stats.isDirectory()) { pathContent.push({ path: pathName, name: file, type: 'Directory', }); } } catch (err) { console.log(`${err}`); } } return pathContent; } app.post('/api/files', (req, res, next) => { const newPath = req.body.path; getPathContent(newPath).then((pathContent) => { res.json(pathContent); }, (err) => { res.status(422).json({ message: `${err}` }); }) })
Et vous ne devez pas concaténer les chemins en utilisant +
( absPath + '/' + file
), utilisez path.join (absPath, file) ou path.resolve (absPath, file)
à la place.
Et vous ne devriez jamais écrire votre code d'une manière qui le code exécuté pour la requête, relais sur des variables globales comme var pathName = '';
et const pathContent = [];
. Cela peut fonctionner dans votre environnement de test, mais entraînera certainement des problèmes en production. Où deux requêtes travaillent sur la variable en "même temps"
Si vous mappez
sur les fichiers avec une fonction asynchrone et Promise.all
, vous obtenez un peu d'augmentation des performances, car toutes les statistiques peuvent s'exécuter en parallèle, au lieu d'être séquentiellement . ( pathContent = wait Promise.all (files.map (async (file) => {wait stat (…); return {chemin: chemin d'accès…}}))
)
@GarrettMotzner Cela dépend du cas d'utilisation si vous avez un tas de requêtes parallèles frappant votre système, alors vous voudrez peut-être rester avec une solution séquentielle par requête.
C'est une préoccupation intéressante, mais je pense que cela serait mieux résolu en limitant les taux ailleurs. Pour moi, cela n'a pas de sens dans la plupart des cas de rendre les appels statistiques séquentiels, car ils n'ont aucune dépendance entre les appels. Il peut y avoir des cas extrêmes où vous vous retrouvez avec trop d'appels système parallèles, mais si tel est le cas, vous avez probablement de plus gros problèmes.
@GarrettMotzner Je ne veux pas contredire ce que vous avez dit au départ. Et oui, la limite de taux doit alors être ajoutée ailleurs. Mais l'avoir séquentiel, puis la charge par rapport au nombre de demandes actuellement actives est plus prévisible, et la limite de débit peut être plus facile à gérer. Mais quelle solution est la meilleure dépend du cas d'utilisation. S'il s'agit d'une action rare, un coup d'oeil avec map et Promise.all pourrait être mieux.
Est-ce que l'un ou l'autre de vous voudrait expliquer pourquoi readdir n'a pas besoin d'être inclus dans le bloc try-catch mais juste stat? Merci!
@sheIsTrue le cas d'erreur de readdir
est intercepté par le deuxième rappel si le `getPathContent (newPath) .then (´
@ t.niese Merci pour l'explication. Alors, pourquoi stat
ne peut-il pas être traité de la même manière que readdir
et doit être à l'intérieur du bloc try-catch? Désolé, je suis très nouveau dans ce domaine.
@sheIsTrue dépend de ce que vous voulez réaliser. Votre code d'origine ignore uniquement les fichiers pour lesquels une erreur s'est produite. Si vous supprimez le bloc try-catch
, alors le 422
serait émis si les stats entraînaient une erreur pour au moins un des fichiers, même si stats donnerait un résultat pour les autres fichiers.
Voici quelques options:
Sync
). Plus lent, mais un changement de code assez simple et très facile à comprendre. util.promisify
) pour créer une promesse pour chaque statistique, et Promise.all
pour attendre que toutes les statistiques soient terminées. Après cela, vous pouvez utiliser les fonctions asynchrones et attendre également pour un code plus facile à lire et une gestion des erreurs plus simple. (Probablement le changement de code le plus important, mais il rendra le code asynchrone plus facile à suivre) res.json
dans le rappel de statistiques (plus petit changement de code, mais très erreur enclin) Sur la base du commentaire initial que j'ai reçu et de la référence, j'ai utilisé readdirSync et statSync à la place et j'ai pu le faire fonctionner. J'examinerai également d'autres réponses et j'apprendrai d'autres façons de mettre en œuvre cela.
Merci à tous pour vos aimables contributions.
Voici ma solution.
const express = require('express'); const bodyParser = require('body-parser'); const fs = require('fs'); const path = require('path'); var pathName = ''; const pathContent = []; app.post('/api/files', (req, res) => { const newPath = req.body.path; // validate path let files; try { files = fs.readdirSync(newPath); } catch (err) { res.status(422).json({ message: `${err}` }); return; } // set the pathName and empty pathContent pathName = newPath; pathContent.length = 0; // iterate each file let absPath = path.resolve(pathName); files.forEach(file => { // get file info and store in pathContent let fileStat = fs.statSync(absPath + '/' + file); if (fileStat.isFile()) { pathContent.push({ path: pathName, name: file.substring(0, file.lastIndexOf('.')), type: file.substring(file.lastIndexOf('.') + 1).concat(' File'), }) } else if (fileStat.isDirectory()) { pathContent.push({ path: pathName, name: file, type: 'Directory', }); } }); res.json(pathContent); });
Vous devriez vraiment déplacer var pathName
et const pathContent
dans votre (req, res) => {...}
afin qu'ils ne soient pas globaux plus, sinon vous obtiendrez des résultats erronés "aléatoires" inattendus si vous avez deux requêtes ou plus en même temps.
Dans ce cas (c'est pour une affectation scolaire), il n'y aura qu'une seule demande à la fois. Le serveur doit les conserver en tant que variables globales pour d'autres requêtes (c'est-à-dire get) pour obtenir également les mêmes informations. (à moins qu'il n'y ait une meilleure approche) J'apprécie vraiment votre précieuse contribution btw. Je viens de tester votre code et de vérifier qu'il fonctionne. Accepter votre réponse comme réponse.
Jetez un œil à cet article, on dirait qu'ils ont utilisé les méthodes synchrones stackoverflow.com/questions/44019316/...
Merci pour la réponse rapide! Sur la base de la référence, j'ai utilisé les méthodes synchrones (readdirSync et statSync) et je l'ai fait fonctionner.