2015-11-18 5 views
0

Итак, я по-прежнему переношу приложение Python на Node.js по разным причинам. У меня есть умеренные знания Javascript через веб-разработку, но у меня есть некоторые проблемы с переменной областью действия и (возможно?) Некоторые асинхронные вызовы, которые я делаю.Область переменной в структуре вложенных функций «Класс» в Javascript

Итак, у меня есть вложенный метод «xmlToObjectByType» в моем классе DataLoader, который я пытаюсь потенциально установить для переменных в зависимости от некоторых критериев соответствия в серии XML-файлов. Независимо от того, что я делаю в методе, продукты никогда не меняются от нуля, а вызов 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> 

ответ

1

Macmee верен тем, что переменные data и product являются только ча nging в рамках обратного вызова 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.

Update

Вот переписан из вашего приложения, которые будут собирать данные из нескольких файлов 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); 

}); 
+0

Так что, похоже, я пропустил асинхронные подсказки на нескольких уровнях. Это полностью имеет смысл и работает. Единственное, на что я немного расплывчато, и я не упоминаю в этом вопросе ради краткости: я на самом деле в конечном итоге вызываю xmlToObjectByType три раза для трех разных типов файлов типа XML; задаваясь вопросом, есть ли разумный способ запустить обратный вызов с результатами всех трех вызовов xmlToObjectByType после их увольнения ... моя первоначальная мысль заключается в том, что вызовы xmlToObj внутри обратного вызова, но я могу понять, почему это явно не сработает. –

+0

Похоже, вы задаете себе правильные вопросы. Чтобы получить то, что вам нужно, вы должны знать, какие области вы создаете, когда они создаются и когда они дают результаты. Именно здесь асинхронный мир может сначала отключить людей. Это требует небольшой корректировки. То, что я сделал, обновлено моим ответом с переписанием вашего модуля, который поместит второй груз данных XML в возвращаемые данные. Если вы можете попытаться собрать его, он должен продемонстрировать, как вы можете столкнуться с проблемами сферы/асинхронности в будущем. – shennan

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

Да, это полностью имеет смысл --- Я думаю, я был на полпути и просто не сделал см. это, глядя на него так долго. Будет подтверждать, но довольно уверен, что все было так просто! –

+0

Так что это имеет смысл в этом контексте, я могу установить данные там, но я не могу получить эту переменную данных за пределами вызова fs.readdir в основной функции; похоже, что это все еще вне сферы действия (или это проблема асинхронности в действительности?) –

+0

Думаю, я должен сказать, что в конечном итоге я хочу вернуть значение данных из основной функции DataLoader(), в дикий, дикий мир. –

Смежные вопросы