Node.js как ждать при асинхронном вызове (readdir и stat) - PullRequest
0 голосов
/ 04 января 2019

Я работаю над методом post на стороне сервера, чтобы получить все файлы в запрошенном каталоге (не рекурсивно), и ниже приведен мой код.

У меня проблемы с отправкой ответа (res.json(pathContent);) с обновленным pathContent без использования setTimeout.

Я понимаю, что это связано с асинхронным поведением используемых методов файловой системы (readdir и stat) и требует использования некоторого рода методов обратного вызова, асинхронного или обещания.

Я пытался использовать async.waterfall со всем телом 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);
});

Ответы [ 4 ]

0 голосов
/ 04 января 2019

На основании полученного мной начального комментария и ссылки я использовал 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);
});
0 голосов
/ 04 января 2019

Вот несколько вариантов:

  • Используйте методы синхронного файла (проверьте документы, но они обычно заканчиваются на Sync). Более медленное, но довольно простое изменение кода и очень простое для понимания.
  • Используйте обещания (или util.promisify), чтобы создать обещание для каждой характеристики, и Promise.all, чтобы дождаться завершения всей статистики. После этого вы можете использовать асинхронные функции и ожидать, что облегчит чтение кода и упростит обработку ошибок. (Возможно, самое большое изменение кода, но это облегчит выполнение асинхронного кода)
  • Ведите счетчик количества статистики, которую вы сделали, и если это число соответствует ожидаемому размеру, тогда вызовите res.json форму внутри обратного вызова статистики (наименьшее изменение кода, но очень подверженное ошибкам) ​​
0 голосов
/ 04 января 2019

Есть другой способ сделать это:

  1. Вы можете сначала выполнить обещание функции с помощью новой функции Promise (), а затем - с помощью async / await или .then ()
  2. Вы можете использовать функцию ProsifyAll () пакета Bluebird (https://www.npmjs.com/package/bluebird)
  3. Вы можете использовать синхронизированную версию функций fs.
0 голосов
/ 04 января 2019

Самый простой и чистый способ - использовать await / async, таким образом, вы можете использовать обещания, и код будет выглядеть почти как синхронный код.

Для этого вам нужна обещанная версия readdir и stat, которую можно создать с помощью promisify ядра utils core.

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 = [];. Это может работать в вашей среде тестирования, но наверняка приведет к проблемам в производстве. Где два запроса работают на переменную в «одновременно»

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...