Function behaving asynchronously even after a callback function in Node.js

心不动则不痛 提交于 2019-12-24 21:28:51

问题


I am attempting to make a file explorer using the 'fs' module of Node.js.

I wrote the following function which takes in a path and stores the contents of that path(files/folders) in an array and just sends it back using the callback.

listDir: function (path, myCallback) {
    var resultObj = [];

    fs.readdir(path, function (err, data) {
        console.log('In listDir');
        if (err) {
            return myCallback(err, null);
        } else {

            data.forEach(function (value) {

                fs.lstat(path + '/' + value, function (err, stats) {

                    if (err) {
                        console.log(err);
                    } else {
                        //console.log('Size-' + stats.isFile()+'\n');
                        if(stats.isFile()){
                            //console.log('is file----\n');
                            /*resultObj.push({
                                type: 'file',
                                name: value,
                                size: stats['size']
                            });*/
                            resultObj.push(value);
                            console.log(resultObj+'\n');

                        } else if(stats.isDirectory()){
                            //console.log('is folder----\n');
                            /*resultObj.push({
                                type: 'folder',
                                name: value,
                                size: stats['size']
                            });*/
                            resultObj.push(value);
                            console.log(resultObj+'\n');
                        }
                    }
                });

            });
            console.log('Resultant obj-' + resultObj);
            return myCallback(null, resultObj);
        }
    });

}

For each file/folder stored in 'data', i'm trying to check whether it is a file or a folder and depending on that i want to push an object into my 'resultObj' array. However, the return statement gets executed even before the forEach is complete and hence i get an empty resultObj everytime.

Why does that happen ? Why does it behave asynchronously even when i have provided a callback function ?

The output is something like this when i call the /test route with the path param--

In listDir
Resultant obj-
GET /test?path=F:\dummyFolder 304 15.210 ms - -
file1
file1,file2
file1,file2,folder3

Please help me with this, i'm really stuck with this assignment which has to be completed by tonight. And also it has been specifically mentioned in the assignment NOT TO USE ANY SYNCHRONOUS FUNCTION OF THE 'fs' MODULE. So how can i do this keeping that clause in mind ?

PS: Don't mind the comments, i used them for debugging.


回答1:


The problem is that you're not waiting for the fs.lstat() function to finish for each file. You're queueing up all the fs.lstat() calls but then not waiting for all of them to finish. You're just going to return back resultObj when it is still [].

You can fix this by adding a check inside the callback of your data.forEach() to see how many of the items have had the callback for fs.lstat() invoked.

listDir: (path, myCallback) => {
    let resultObj = [];

    fs.readdir(path, (err, data) => {
      console.log('In listDir');
      if (err) {
          return myCallback(err);
      }

      let itemsCompleted = 0        
      data.forEach(value => {
        fs.lstat(path + '/' + value, (err, stats) => {
          itemsCompleted++
          if (err) {
            console.log(err);
          } else {
            if(stats.isFile() || stats.isDirectory()){
              resultObj.push(value);
              console.log(resultObj+'\n');
            }
          }

          if(itemsCompleted >= data.length) {
            return myCallback(null, resultObj)
          }
        });            
    });
}

However, the above approach is still more complicated than Promises with async/await. Promises with async/await is currently the preferred way to handle asynchronous control flow in Node. Utilizing util.promisify() to promisify fs.readdir() and fs.lstat() allows us to flatten the control flow and dramatically improve the readability of the listDir(). Additionally, by using map() instead of forEach() we can use Promise.all() to wrap the returned Promise from lstatAsync() for each entry.

const {promisify} = require('util')
const fs = require('fs')
const {join} = require('path')
const readdirAsync = promisify(fs.readdir)
const lstatAsync = promisify(fs.lstat)

module.exports = {
  listDir: async path => {
    let resultObj = []; 

    let entries = await readdirAsync(path)
    console.log('In listDir');

    await Promise.all(entries.map(async value => {
      try {
        let stats = await lstatAsync(join(path, value))

        if(stats.isFile() || stats.isDirectory()){
          resultObj.push(value);
          console.log(resultObj+'\n');
        }
      } catch (err) {
        console.log(err)
      }
    }))

    return resultObj
  }
}


来源:https://stackoverflow.com/questions/48272330/function-behaving-asynchronously-even-after-a-callback-function-in-node-js

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!