Javascript Node.js如何等待异步调用(readdir和stat)

Javascript Node.js如何等待异步调用(readdir和stat),javascript,node.js,express,asynchronous,Javascript,Node.js,Express,Asynchronous,我正在服务器端使用post方法来检索请求目录中的所有文件(不是递归的),下面是我的代码。 我无法使用更新的pathContent返回响应(res.json(pathContent);),而不使用setTimeout 我知道这是由于使用的文件系统方法(readdir和stat)的异步行为造成的,需要使用某种回调、异步或承诺技术 我尝试使用async.failter,将整个readdir作为一个函数,将res.json(pathContent)作为另一个函数,但它没有将更新的数组发送到客户端 我知道

我正在服务器端使用post方法来检索请求目录中的所有文件(不是递归的),下面是我的代码。

我无法使用更新的
pathContent
返回响应(
res.json(pathContent);
),而不使用
setTimeout

我知道这是由于使用的文件系统方法(
readdir
stat
)的异步行为造成的,需要使用某种回调、异步或承诺技术

我尝试使用
async.failter
,将整个
readdir
作为一个函数,将
res.json(pathContent)
作为另一个函数,但它没有将更新的数组发送到客户端

我知道关于这个异步操作有成千上万的问题,但在阅读了大量的文章后,我还不知道如何解决我的问题

如有任何意见,将不胜感激。谢谢

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

最简单、最干净的方法是使用
await
/
async
,这样您就可以利用承诺,代码看起来几乎像同步代码

因此,您需要一个
readdir
stat
的promisified版本,它可以由
utils
核心库的
promisify
创建

const{promisify}=require('util'))
const readdir=promisify(require('fs').readdir)
const stat=promisify(require('fs').stat)
异步函数getPathContent(newPath){
//移动路径内容否则可能与并发请求发生冲突
常量pathContent=[];
let files=wait readdir(newPath)
让pathName=newPath;
//pathContent.length=0;//不再需要,因为每个请求的pathContent都是新的
常量absPath=path.resolve(路径名);
//迭代每个文件
//将forEach替换为(for…of),因为这样更容易
//使用“异步”的步骤
//否则,您需要使用files.map和Promise.all
for(让文件中的文件){
//获取文件信息并存储在pathContent中
试一试{
let stats=wait stat(absPath+'/'+文件)
if(stats.isFile()){
路径内容推送({
路径:路径名,
名称:file.substring(0,file.lastIndexOf('.'),
类型:file.substring(file.lastIndexOf('.')+1.concat('file'),
})
}else if(stats.isDirectory()){
路径内容推送({
路径:路径名,
名称:文件,
键入:“目录”,
});
}
}捕捉(错误){
log(`${err}`);
}
}
返回路径内容;
}
app.post('/api/files',(req,res,next)=>{
const newPath=req.body.path;
getPathContent(newPath)。然后((pathContent)=>{
res.json(路径内容);
},(错误)=>{
res.status(422).json({
消息:`${err}`
});
})
})
您不应该使用
+
absPath+'/'+file
)连接路径,而是使用
path.join(absPath,file)
path.resolve(absPath,file)

而且,您永远不应该以为请求执行的代码依赖于全局变量(如
var pathName='')的方式编写代码
常量路径内容=[]。这可能在您的测试环境中起作用,但肯定会导致生产中出现问题。如果两个请求“同时”处理变量,这里有一些选项:

  • 使用同步文件方法(检查文档,但它们通常以
    Sync
    结尾)。速度较慢,但代码更改相当简单,并且非常容易理解
  • 使用承诺(或
    util.promisify
    )为每个统计创建承诺,使用
    promise.all
    等待所有统计完成。之后,您可以使用异步函数,并等待更易于阅读的代码和更简单的错误处理。(可能是最大的代码更改,但它将使异步代码更易于遵循)
  • 保留一个计数器,记录您已完成的统计数据的数量,如果该数字是您期望的大小,则在stat回调中调用
    res.json
    表单(最小的代码更改,但非常容易出错)
有不同的方法:
  • 您可以首先使用new Promise()提示函数,然后使用async/await或.then()提示函数
  • 您可以使用Bluebird包()的函数ProsifyAll()
  • 您可以使用fs函数的同步版本

  • 根据我收到的最初评论和参考资料,我使用了readdirSync和statSync,并且能够使其正常工作。我还将回顾其他答案,并了解实现这一点的其他方法

    谢谢你们所有人的投入

    这是我的解决办法

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

    看看这篇文章,看起来他们使用了同步方法,谢谢你的快速回复!根据参考资料,我使用了同步方法(readdirSync和statSync)并使其正常工作。如果您使用异步函数和Promise.all
    map
    对文件进行映射,您将获得一点性能提升,因为所有统计数据都可以并行运行,而不是按顺序运行。(
    pathContent=await Promise.all(files.map(async(file)=>{await stat(…);return{path:pathname…}))
    )@GarrettMotzner,这取决于用例如果您的系统中有大量并行请求,那么您可能希望每个请求都使用顺序解决方案。这是一个有趣的问题,但我认为最好通过其他地方的利率限制来解决这一问题。对我来说,在大多数情况下,让stat调用顺序化是没有意义的,因为这样调用之间就没有依赖关系了。在某些极端情况下,可能会有太多的并行系统调用,但如果是这样的话,可能会有更大的问题。@GarrettMotzner我不想反驳你最初所说的。及