簡體   English   中英

Node.js如何等待異步調用(readdir和stat)

[英]Node.js how to wait on asynchronous call (readdir and stat)

我正在服務器端的post方法檢索所請求目錄中的所有文件(不是遞歸),下面是我的代碼。

我無法使用更新的pathContent發送響應( res.json(pathContent); )而不使用setTimeout

我知道這是由於所使用的文件系統方法的異步行為( readdirstat ),需要使用某種回調,異步或承諾技術。

我試圖使用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);
});

最簡單和最干凈的方法是使用await / async ,這樣你可以使用promises,代碼幾乎就像同步代碼。

因此,您需要一個可以通過utils核心庫的promisify創建的readdirstat的promisified版本。

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.join(absPath, file)path.resolve(absPath, file)

而且你永遠不應該以代碼為請求執行的方式編寫代碼,繼續var pathName = '';全局變量,如var pathName = ''; const pathContent = []; 這可能在您的測試環境中有效,但肯定會導致生產中出現問題。 兩個請求在“同時”處理變量的位置

這里有一些選擇:

  • 使用同步文件方法(檢查文檔,但它們通常以Sync結尾)。 較慢,但代碼更改相當簡單,而且非常容易理解。
  • 使用promises(或util.promisify )為每個stat創建一個promise, Promise.all等待所有統計信息完成。 之后,您可以使用異步函數並等待更容易閱讀的代碼和更簡單的錯誤處理。 (可能是最大的代碼更改,但它會使異步代碼更容易遵循)
  • 保持你已經完成的統計數量的計數器,如果這個數字是你期望的大小,那么在stat回調中調用res.json表單(最小的代碼更改,但非常容易出錯)

有不同的方法:

  1. 您可以先使用新的Promise()然后使用async / await或.then()來宣傳該函數。
  2. 您可以使用Bluebird包的功能ProsifyAll()( https://www.npmjs.com/package/bluebird
  3. 您可以使用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);
});

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM