【问题标题】:Variable scope in nested function "Class" structure in JavascriptJavascript中嵌套函数“类”结构中的变量范围
【发布时间】:2016-02-19 13:42:22
【问题描述】:

因此,出于各种原因,我正在将 Python 应用程序移植到 Node.js。我通过 Web 开发获得了适度的 Javascript 知识,但在变量范围和(可能?)我正在进行的一些异步调用方面遇到了一些问题。

所以,我的 DataLoader 类中有一个嵌套方法“xmlToObjectByType”,我试图根据一系列 XML 文件中的一些匹配条件来设置变量。无论我在该方法中做什么,产品都不会从 null 更改,并且 xml_files.splice() 调用永远不会起作用。我很肯定这是一个范围界定问题(在 fs.readFile 中的 parseString 中的代码,在 forEach 中等等),但我找不到太多运气来弄清楚究竟是为什么或如何正确获取值集。

最后,我尝试在 xmlToObjectByType 上设置的回调中获取结果,这得到了我正在寻找的值,但我仍然无法从回调中设置 products 的值。我确定这与范围界定有关,但我有点茫然。我确信这是我忽略的一件非常简单的事情,但是自从我深入研究 JS 以来已经有很长时间了。这根本不是很深。除了可能很糟糕的逻辑流程之外,对我在这里做错了什么有任何想法吗?

请注意,这是一个简化版本,出于易读性原因,我在其中检查了一些其他 XML 文件类型。

代码

var fs = require('fs'),
    xml2js = require('xml2js');


export function DataLoader(working_directory){  
  var working_directory = working_directory;
  var xml_files = [];
  var products = null;
  var data = null;

  var xmlToObjectByType = function(type, setValue) {
    xml_files.forEach(function(file, index) {
        var parser = new xml2js.Parser();
        fs.readFile(working_directory + '/' + file, function(err, data) {
            parser.parseString(data, function (err, result) {
                if (result.Products.Product) {
                    var result_object = result.Products.Product;
                    // check if we've got at least one row, else return false
                    if (result_object.length > 0) {

                        // products specific check
                        if (type == "products") {
                            // identify products XML with artist tag
                            if (result_object[0].Artist) {
                                // this is a products XML file, so pop this file from xml_files, return object
                                xml_files.splice(index, 1);
                                setValue(result_object);
                            } 
                        }

                    } else {
                        // no rows in object
                        setValue("no rows");
                    }
                } else {
                    // ROW object isn't set, malformed XML
                    setValue("malformed XML");
                }
            });
        });
    })
  }

  // check selected directory for XML files
    fs.readdir(working_directory,function(err,files){
        if(err) throw err;
        files.forEach(function(file){
            // do something with each file HERE!
            if (file.split('.').pop() == "xml") {
                xml_files.push(file);
            }
        });

            // if they don't exist return and send message
            if (xml_files.length < 1) {
                var status = {status: "error", message: "There are no XML files in the directory you selected."};
            } else {

                // process further
                xmlToObjectByType("products", function(result) {
                    products = result;
                });


                data = {"products": products};

                // products always has the value null here
                console.log(data);
            }

            return status;
     });
};

我是通过

import { DataLoader } from './my_module';
DataLoader('/Path/To/XML');

还有一个 XML 文件的简化示例(我认为我做对了)

<?xml version="1.0" encoding="UTF-8" ?>
<Products>
    <Product>
        <Artist>Test</Artist>
        <Title>Test Title</Title>
        <Description>Maecenas faucibus mollis interdum. Donec sed odio dui. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Aenean lacinia bibendum nulla sed consectetur. Cras mattis consectetur purus sit amet fermentum.</Description>
    </Product>
    <Product>
        <Artist>Test</Artist>
        <Title>Test Title</Title>
        <Description>Maecenas faucibus mollis interdum. Donec sed odio dui. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Aenean lacinia bibendum nulla sed consectetur. Cras mattis consectetur purus sit amet fermentum.</Description>
    </Product>
    <Product>
        <Artist>Test</Artist>
        <Title>Test Title</Title>
        <Description>Maecenas faucibus mollis interdum. Donec sed odio dui. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Aenean lacinia bibendum nulla sed consectetur. Cras mattis consectetur purus sit amet fermentum.</Description>
    </Product>
    <Product>
        <Artist>Test</Artist>
        <Title>Test Title</Title>
        <Description>Maecenas faucibus mollis interdum. Donec sed odio dui. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Aenean lacinia bibendum nulla sed consectetur. Cras mattis consectetur purus sit amet fermentum.</Description>
    </Product>
</Products>

【问题讨论】:

    标签: javascript node.js asynchronous scope


    【解决方案1】:

    Macmee 是正确的,因为您的 dataproduct 变量仅在 xmlToObjectByType 回调的范围内发生变化,该回调被异步调用(处理器的下一个滴答声,在您请求结果之后记录到控制台)。

    但我认为您的主要问题不在于您对数据的处理,而是您在代码中请求有关数据的反馈。

    因此,使用您当前的代码,products 将填充一个表示 XML 的对象,但它只是在您要求将其打印回给您之后才被填充。您可以通过放置一个简单的定时函数来测试这个理论,该函数将在半秒内报告结果:

    /* ... code before */
    
    var working_directory = working_directory;
    var xml_files = [];
    var products = null;
    var data = null;
    
    setTimeout(function(){
    
      console.log(products);
    
    }, 500);
    
    /* code after... */
    

    所以,一般来说,您真正想要的是 callback 用于 DataLoader

    function DataLoader(working_directory, callback){ 
    

    并在您的 XML 解析回调中调用它:

    xmlToObjectByType("products", function(result) {
        callback({"products": result});
    });
    

    然后像这样打电话给你的DataLoader

    DataLoader('/Path/To/XML', function(data){ console.log(data) });
    

    通过对代码的这些调整,我在控制台中获得了以下输出:

    { products: 
       [ { Artist: [Object], Title: [Object], Description: [Object] },
         { Artist: [Object], Title: [Object], Description: [Object] },
         { Artist: [Object], Title: [Object], Description: [Object] },
         { Artist: [Object], Title: [Object], Description: [Object] }
       ]
    }
    

    显然,我正在对您的用户案例做出一些假设。但我认为这展示了如何在异步 JavaScript 中导航不同的范围。

    更新

    这是您的应用程序的重写,它将从多个 XML 文件中整理数据,然后使用数据进行回调:

    var fs = require('fs'),
        xml2js = require('xml2js'),
        path = require('path');
    
    
    export function DataLoader (directory, callback) {  
    
      getXmlFiles( function (files) {
    
        parseXmlFile(files, callback);
    
      } );
    
      function parseXmlFile (files, callback) {
    
        var parser = new xml2js.Parser();
        var data = {};
        var filesLeft = files.length;
    
        files.forEach( function(file, i) {
    
          fs.readFile( path.join(directory, file), function (err, result) {
    
            parser.parseString( result, function (err, result) {
    
              if (result.Products.Product) {
    
                var result_object = result.Products.Product;
    
                if (result_object.length > 0 && result_object[0].Artist) {
    
                  data[file] = result_object;
    
                }
              }
    
              filesLeft--;
    
              if (!filesLeft)
                callback(data);
    
            });
          });
        });
      }
    
      function getXmlFiles (callback) {
    
        var files = [];
    
        fs.readdir(directory, function (err, f) {
    
          if(err) throw err;
    
          f.forEach( function (file) {
    
            if (file.split('.').pop() == "xml") {
    
              files.push(file);
    
            }
          });
    
          callback(files);
    
        });
      }
    }
    

    将它与这样的东西一起使用:

    DataLoader( __dirname, function (data) {
    
      console.log(data);
    
    });
    

    【讨论】:

    • 所以看起来我错过了几个级别的异步线索。这完全有道理并且有效。有一件事我有点模糊,为了简洁起见,我没有在问题中提及:我实际上最终调用了 xmlToObjectByType 三次以进行三种不同的 XML 文件类型检查;想知道是否有一种理智的方法可以在所有三个 xmlToObjectByType 调用被触发后触发回调......我最初的想法是将 xmlToObj 调用放在回调中,但我明白为什么这显然不起作用。
    • 听起来您在问自己正确的问题。为了得到你所追求的,你必须知道你正在创建什么范围,什么时候创建的,什么时候产生结果。这就是异步世界最初可能会绊倒人们的地方。它需要一点调整。我所做的是通过重写您的模块来更新我的答案,这将在返回的数据中放入第二次 XML 数据加载。如果您可以尝试并与之合作,它应该会展示您将来如何处理范围/异步问题。
    • 哇,谢谢你这样做——不是必须的,但很高兴看到。这完全是有道理的,回调是如何沿着函数背负的,也有道理为什么它会让人失望。非常感谢!
    【解决方案2】:

    这部分在这里:

    xmlToObjectByType("products", function(result) {
        products = result;
    });
    
    data = {"products": products};
    console.log(data);
    

    我假设xmlToObjectByType 的回调是异步的,因此:

    data = {"products": products};
    console.log(data);
    

    这将在之前运行:

    products = result;
    

    这意味着当您将值设置为data 时,它将是未定义的。试试:

    xmlToObjectByType("products", function(result) {
        products = result;
        data = {"products": products};
        callback(status, data); // see below
    });
    

    如果您想在调用DataLoader 后访问data,那么DataLoader 现在必须进行回调:

    function DataLoader(working_directory, callback)
    

    那么在您致电DataLoader 时,您需要执行以下操作:

    DataLoader(.., function(status, data) {
      // do stuff with status and data
    })
    

    【讨论】:

    • 是的,这完全有道理——我想我已经走到了一半,只是盯着它看了这么久之后才发现。会确认,但很确定就是这么简单!
    • 所以从上下文来看这是有道理的,我可以在那里设置数据,但我无法在主函数中的 fs.readdir 调用之外获取该数据变量;看起来这仍然超出了范围(或者实际上是异步问题?)
    • 我想我应该说最终我想从主要的 DataLoader() 函数返回数据值,进入狂野的世界。
    猜你喜欢
    • 2013-05-03
    • 2012-06-24
    • 2015-07-02
    • 1970-01-01
    • 2016-09-25
    • 2012-09-14
    • 1970-01-01
    • 2011-07-10
    • 1970-01-01
    相关资源
    最近更新 更多