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

时间:2019-01-04 20:02:31

标签: javascript node.js express asynchronous

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

我在不使用res.json(pathContent);的情况下用更新的pathContent发回响应(setTimeout)遇到困难。

我知道这是由于所使用的文件系统方法(readdirstat)的异步行为引起的,并且需要使用某种回调,异步或promise技术。

我尝试使用async.waterfallreaddir的整个正文作为一个函数,而将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);
});

4 个答案:

答案 0 :(得分:3)

最简单,最干净的方法是使用await / async,这样您就可以利用Promise,并且代码几乎就像同步代码一样。

为此,您需要readdir核心库的stat创建的promisifyutils的简化版本。

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

并且您不应该使用+absPath + '/' + file)连接路径,而应使用path.join(absPath, file)path.resolve(absPath, file)

并且永远不要以为请求执行的代码,像var pathName = '';const pathContent = [];这样的全局变量为基础来编写代码。这可能在您的测试环境中有效,但是肯定会导致生产中出现问题。其中两个请求在“相同时间”

处对该变量起作用

答案 1 :(得分:0)

以下是一些选择:

  • 使用同步文件方法(请检查文档,但它们通常以Sync结尾)。速度较慢,但​​代码更改相当简单,而且非常易于理解。
  • 使用promise(或util.promisify)为每个统计信息创建一个promise,并使用Promise.all等待所有统计信息完成。之后,您可以使用异步函数并等待,以便于阅读代码和简化错误处理。 (可能是最大的代码更改,但它将使异步代码更易于遵循)
  • 保留一个已完成的统计数量的计数器,如果该数量符合您的预期大小,请在stat回调内调用res.json表单(代码更改最少,但容易出错)

答案 2 :(得分:0)

有不同的方法:

  1. 您可以首先使用新的Promise()来实现函数的形式化,然后再使用async / await或.then()
  2. 您可以使用Bluebird软件包(https://www.npmjs.com/package/bluebird)的ProsifyAll()函数
  3. 您可以使用fs函数的同步版本

答案 3 :(得分:0)

根据我收到的初始评论和参考,我改用了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);
});