9

I'm building a server in Node that will search a folder to see if an XML file exists (glob), and if it does, read the file in (fs) as a JSON object (xml2js) and eventually store it in a database somewhere. I'm want to get the results OUT of the parser and into another variable so I can do other things with the data. From what I can tell, something is running synchronously, but I can't figure out how to stop it and for me to wait until it's finished to continue moving on.

I'm separating my function out into a controller elsewhere from app.js:

app.controller.js

const fs = require('fs-extra');
const glob = require('glob');
const xml2js = require('xml2js');

exports.requests = {};

exports.checkFileDrop = async () => {
  console.log('Checking for xml in filedrop...');
  // this is the only place await works...
  await glob('./filedrop/ALLREQUESTS-*.xml', (err, files) => {
    var parser = new xml2js.Parser();
    // this is looking for a specific file now, which I'll address later once I can figure out this issue
    fs.readFile('./filedrop/ALLREQUESTS-20170707.xml', 'utf16le', function (err, data) { 
      if (err) {
        console.log('ERROR: ', err);
      } else {
        parser.parseString(data, (err, result) => {
          if (err) {
            console.log('ERROR: ', err);
          } else {
            console.log('data found');
            exports.requests = JSON.stringify(result.Records.Record);
            // data is outputted here correctly
            console.log(exports.requests);
            // this doesn't even seem to want to save to exports.requests anyways...
          }
        });
      }
    });
  });
}

app.js

const appController = require('./controllers/app.controller');

// check if there is file in filedrop
appController.checkFileDrop();
// prints out an empty object
console.log(appController.requests);
// can't do anything if it doesn't exist yet
appController.saveToDB(appController.requests);
Mike
  • 107
  • 1
  • 1
  • 5

2 Answers2

16

await will wait for a Promise value to resolve, otherwise it'll just wrap the value it is given in a promise and resolve the promise right away. In your example,

await glob('./filedrop/ALLREQUESTS-*.xml', (err, files) => {

the call to glob does not return a Promise, so the await is essentially useless. So you need to create the promise yourself.

exports.checkFileDrop = async () => {
  console.log('Checking for xml in filedrop...');

  const files = await new Promise((resolve, reject) => glob('./filedrop/ALLREQUESTS-*.xml', (err, files) => {
    if (err) reject(err);
    else resolve(files);
  });

  const parser = new xml2js.Parser();

  const data = await new Promise((resolve, reject) => fs.readFile('./filedrop/ALLREQUESTS-20170707.xml', 'utf16le', function (err, data) {
    if (err) reject(err);
    else resolve(data);
  });

  const result = await new Promise((resolve, reject) => parser.parseString(data, (err, result) => {
    if (err) reject(err);
    else resolve(result);
  });

  console.log('data found');

  const requests = JSON.stringify(result.Records.Record);

  console.log(requests);
}

Note that now this function will reject the promise it returns instead of force-logging the error.

You can also condense this down with a helper. Node 8 for instance includes util.promisify to make code like this easier to write, e.g.

const util = require('util');

exports.checkFileDrop = async () => {
  console.log('Checking for xml in filedrop...');

  const files = await util.promisify(glob)('./filedrop/ALLREQUESTS-*.xml');
  const parser = new xml2js.Parser();

  const data = await util.promisify(fs.readFile)('./filedrop/ALLREQUESTS-20170707.xml', 'utf16le');

  const result = await util.promisify(parser.parseString.bind(parser))(data);

  console.log('data found');

  const requests = JSON.stringify(result.Records.Record);

  console.log(requests);
}
loganfsmyth
  • 156,129
  • 30
  • 331
  • 251
  • 1
    so now that i'm creating promises for all 3 parts of this function, how do i get that `requests` object into my other function, without passing it in directly? – Mike Jul 26 '17 at 17:40
  • You're `return requests;` and then wherever you use `checkFileDrop` you'd either `await` its result, or do `.then` on the promise to get the value. – loganfsmyth Jul 26 '17 at 17:45
  • duh! i was just trying this out and was still using my exports.requests unknowingly, which was still giving me an empty object. instead now i just used `appController.checkFileDrop().then((data) => console.log(data));` (for testing) and I got what i wanted. thank you very much for your help! I was lost, getting buried in function after function :) – Mike Jul 26 '17 at 17:50
0

You can use async/await

import fs from 'fs';
import { promisify } from 'util';

const xmlToJson = async filePath => {
  const parser = new xml2js.Parser
  try {
    const data = await fs.promises.readFile(filePath, 'utf8')
    const result = await promisify(parser.parseString)(data);
    const requests = JSON.stringify(result.merchandiser.product);
    return requests
  }
  catch(err) {
    console.log(err)
  }
}
arnaudjnn
  • 923
  • 10
  • 12