Why doesn't fs.readFileSync return anything inside a promise on the side?

I found this "Rendering PDFs with React Components" dedicated to themeteorchef about generating PDFs on the Meteor server and then sent them to the client. could follow a similar approach when generating docx files with officegen

I created a very similar server module that generates a docx file from the inputs on the client machine and then tries to convert them to a base64 string, which then needs to be sent to the client. However, the base64 string is never generated.

Here's the module:

let myModule;

const getBase64String = (loc) => {
  try {
    const file = fs.readFileSync(loc);
    return new Buffer(file).toString('base64');
  } catch (exception) {
    myModule.reject(exception);
  }
}

const generateBase64Docx = (path, fileName) => {
  try {
    myModule.resolve({fileName, base64: getBase64String(path+fileName)});
    fs.unlink(loc);
  } catch (exception) {
    myModule.reject(exception);
  }

}

const formatComponentAsDocx = (props, fileName) => {
  try {
    var docxFile = officegen({
      'type': 'docx',
      'orientation': 'portrait',
      'title': props.title,
    });

    var pObj = docxFile.createP();
    pObj.addText(props.body);

    var path = './';
    output = fs.createWriteStream(path+fileName);

    docxFile.generate(output);
    return path;

  } catch (exception) {
    myModule.reject(exception);
  }

}

const handler = ({props, fileName}, promise) => {
  myModule = promise;
  const path = formatComponentAsDocx(props, fileName);
  if (path) {
    generateBase64Docx(path, fileName);
  }
}

export const generateComponentAsDocx = (options) => {
  return new Promise((resolve, reject) => {
    return handler(options, { resolve, reject });
  });
};
      

Run codeHide result


The problem here is fs.readFileSync . It always returns an empty buffer and so the file is never converted to base64 string and never sent back to the client. Why is that? The file itself is always created on the server and can always be found.

If I change a part const file = fs.readFileSync(loc);

like this

fs.readFile(loc, (err, data) => {
 if(err) myModule.reject(err);
 console.log(JSON.stringify(data));
}

      

I can see some data in the data, but not enough for the whole file.

What am I doing wrong here? Did I miss something?

+3


source to share


3 answers


You need to wait for the file created with officegen

to complete before you try to extract base64 from it. This is the smallest change you need to make. I do not recommend waiting for an event finalize

generated officegen

as it is a buggy event . I recommend waiting on the finish

outflow event . However, there are additional problems with the code you are showing:

  • Since you have a code to unlock the file immediately after using it, I conclude that you do not need the file. This way you can just create the data in memory and get the string base64

    .

  • All rigmarole with myModule

    awful awful design. If one of my colleagues provided code like this, strong words would be exchanged. Yes, that 's that bad. It's much better to convert your entire codebase to work with promises.



The whole module can be summarized as follows. I've done a modicum testing on this code, but I'm not claiming that it deals with every probability.

import * as stream from "stream";
import officegen from "officegen";

function formatComponentAsDocx(props) {
  return new Promise((resolve, reject) => {
    // There no need to wrap this in try...catch only to call reject. If any
    // exception is raised in this function, the promise is automatically
    // rejected.
    const docxFile = officegen({
      'type': 'docx',
      'orientation': 'portrait',
      'title': props.title,
    });

    const pObj = docxFile.createP();
    pObj.addText(props.body);

    // We record the output in our own buffer instead of writing to disk,
    // and reading later.
    let buf = Buffer.alloc(0);
    const output = new stream.Writable({
      write(chunk, encoding, callback) {
        buf = Buffer.concat([buf, chunk]);
        callback();
      },
    });

    docxFile.generate(output, {
      // Do propagate errors from officegen.
      error: reject,
    });

    // We don't use the "finalize" event that docxFile.generate would emit
    // because it is buggy. Instead, we wait for the output stream to emit
    // the "finish" event.
    output.on('finish', () => {
      resolve(buf);
    });
  });
}

export function generateComponentAsDocx({ props }) {
  return formatComponentAsDocx(props).then((data) => {
    return { base64: data.toString("base64") };
  });
};

      

+2


source


Your problem is that docxFile.generate(output);

it is not synchronous. So even though your local path exists (it was created by the call fs.createWriteStream()

), it is empty and your synchronous fs.readFileSync

only catches this empty file.

You have to subscribe to the event docxFile

finalize

to catch the end of file generation:

docxFile.on('finalize, function (writtenBytes) {
  // do you work with generated file here
});

      



Thus, rewriting the code:

const handler = ({props, fileName}, promise) => {
  myModule = promise;
  formatComponentAsDocx(props, fileName);
}

const formatComponentAsDocx = (props, fileName) => {
  try {
    var docxFile = officegen({
      'type': 'docx',
      'orientation': 'portrait',
      'title': props.title,
    });

    var pObj = docxFile.createP();
    pObj.addText(props.body);

    var path = './';
    output = fs.createWriteStream(path+fileName);

    docxFile.on('error', function (err) {
      myModule.reject(err);
    });

    docxFile.on('finalize', function () {
      generateBase64Docx(path, fileName);
    });

    docxFile.generate(output);

  } catch (exception) {
    myModule.reject(exception);
  }
}

      

+2


source


readFileSync is synchronous, so it doesn't deal with promises.

https://nodejs.org/api/fs.html#fs_fs_readfilesync_file_options

Synchronous version of fs.readFile. Returns the contents of the file.

      

You probably want to use fs.readFile.

https://nodejs.org/api/fs.html#fs_fs_readfile_file_options_callback

    The callback is passed two arguments (err, data), where data is the contents of the file.

    If no encoding is specified, then the raw buffer is returned.

      

-2


source







All Articles