简体   繁体   English

JavaScript中嵌套函数“类”结构中的变量范围

[英]Variable scope in nested function “Class” structure in Javascript

So I'm in the process of porting a Python application over to Node.js, for various reasons. 因此,出于各种原因,我正在将Python应用程序移植到Node.js。 I've got moderate Javascript knowledge via web development, but have been having some issues with variable scope and (possibly?) some asynchronous calls I'm making. 我通过网络开发掌握了一定程度的Javascript知识,但是在可变范围和(可能是)我正在进行的一些异步调用方面遇到了一些问题。

So, I've got a nested method "xmlToObjectByType" in my DataLoader class that I'm trying to potentially set variables in, based on some matching criteria in a series of XML files. 因此,我在DataLoader类中有一个嵌套方法“ xmlToObjectByType”,我试图根据一系列XML文件中的某些匹配条件来设置变量。 No matter what I do in the method, products never changes from null, and the xml_files.splice() call never works. 无论我在方法中做什么,产品都不会从null更改,并且xml_files.splice()调用永远不会起作用。 I'm positive it's a scoping issue (code inside parseString which is inside fs.readFile, which is inside forEach, etc etc) but I haven't been able to find much luck figuring out exactly why or how to correctly get the value set. 我很肯定这是一个范围问题(parseString内的代码位于fs.readFile内,forEach内等),但我还没找到很多运气,无法弄清楚为什么或如何正确获取值集。

As a last ditch I tried getting the result in a callback set on xmlToObjectByType, which gets me the value I'm looking for, but I still can't set the value of products from the callback. 作为最后的沟渠,我尝试在xmlToObjectByType上的回调集中获取结果,这使我获得了所需的值,但是我仍然无法从回调中设置products的值。 I'm sure it has to do with scoping, but I'm at a bit of a loss. 我敢肯定这与范围界定有关,但我有点茫然。 I'm sure this is a really simple thing I'm overlooking, but it's been a long time since I've dove this deeply into JS. 我敢肯定这是一件非常简单的事情,但是我已经将它深入到JS中已经很长时间了。 Which isn't very deep at all. 根本不是很深。 Any thoughts on what I'm doing wrong here, aside from the probably terrible logic flow? 除了可能令人恐惧的逻辑流程之外,对我在这里做错的事情有任何想法吗?

Note that this is a simplified version where I took out the checks for a few other XML file types, for legibility reasons. 请注意,这是一个简化版本,出于可读性原因,我检查了其他几种XML文件类型。

Code

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

I'm calling it via 我通过

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

And a simplified example of an XML file (i think i did this right) 和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>

this part here: 这部分在这里:

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

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

the callback to xmlToObjectByType I am assuming is asynchronous, therefore: 我假设的xmlToObjectByType回调是异步的,因此:

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

this will run before: 这将在之前运行:

products = result;

which means when you set a value to data it will be undefined. 这意味着当您为data设置值时,它将是不确定的。 Try: 尝试:

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

and DataLoader will have to now take a callback if you want to access data after calling DataLoader : DataLoader会,如果你要访问到现在采取的回调data调用后DataLoader

function DataLoader(working_directory, callback)

then where you call DataLoader you will need to do: 然后在调用DataLoader需要执行以下操作:

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

Macmee is correct in that your data and product variables are only changing within the scope of the xmlToObjectByType callback, which is being called asynchronously (the next tick of the processor, after you've requested the result to be logged to console). Macmee是正确的,因为您的dataproduct变量仅在xmlToObjectByType回调的范围内更改,该回调被异步调用(在您要求将结果记录到控制台后,处理器的下一个滴答)。

But I think your main problem is not-so-much your handling of the data, but at which point in the code you're requesting for feedback about the data. 但是我认为您的主要问题不是那么多的数据处理,而是您在代码中的哪个点要求获得有关数据的反馈。

So, with your current code, products is getting populated with an Object representing the XML, but it's just being populated far after you've asked it to be printed back to you. 因此,与您现有的代码, products 越来越填充表示XML对象,但它只是被填充你问它要打印回给你之后为止。 You can test this theory by placing a simple timed function which will report back the results in half a second: 您可以通过放置一个简单的定时函数来测试该理论,该函数将在半秒内报告结果:

/* ... code before */

var working_directory = working_directory;
var xml_files = [];
var products = null;
var data = null;

setTimeout(function(){

  console.log(products);

}, 500);

/* code after... */

So, what you really want is a callback for the DataLoader in general: 因此,您真正想要的是通常用于DataLoadercallback

function DataLoader(working_directory, callback){ 

And call it in your XML parsing callback: 并在您的XML解析回调中调用它:

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

And then call your DataLoader like so: 然后像这样调用您的DataLoader

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

With these adjustments to the code, I'm getting this output in my console: 通过对代码的这些调整,我可以在控制台中获得以下输出:

{ 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] }
   ]
}

Obviously I'm making some assumptions about your user-case. 显然,我对您的用例做了一些假设。 But I think this demonstrates how you can navigate the different scopes in asynchronous JavaScript. 但是我认为这说明了如何在异步JavaScript中导航不同的作用域。

Update 更新资料

Here's a re-write of your application that will collate data from multiple XML files and then callback with the data: 这是您的应用程序的重新编写,它将整理来自多个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);

    });
  }
}

Use it with something like this: 将其与以下内容一起使用:

DataLoader( __dirname, function (data) {

  console.log(data);

});

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM