Javascript中嵌套函数“Class”结构中的变量范围

时间:2015-11-18 00:17:11

标签: javascript node.js asynchronous scope

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

所以,我在我的DataLoader类中有一个嵌套方法“xmlToObjectByType”,我试图根据一系列XML文件中的一些匹配条件设置变量。无论我在方法中做什么,产品永远不会从null更改,并且xml_files.splice()调用永远不会起作用。我很肯定这是一个范围问题(parseString里面的代码,里面是fs.readFile,里面是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>

2 个答案:

答案 0 :(得分:1)

这一部分在这里:

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

答案 1 :(得分: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的{​​{1}}:

DataLoader

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

function DataLoader(working_directory, callback){ 

然后像这样打电话给你xmlToObjectByType("products", function(result) { callback({"products": result}); });

DataLoader

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

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

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

<强> 更新

这里是您的应用程序的重写,它将整理来自多个XML文件的数据,然后回调数据:

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

使用类似这样的东西:

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

    });
  }
}
相关问题