6

I have a command line program in Javascript/Node.js that writes data to files.

I want to ask user confirmation to overwrite a file, if it already exists.

This fails:

function writeData (filename, data) {
    var fs = require ('fs');
    try {
        fs.writeFileSync(filename, data, {flag: 'wx'});
    } catch (e) {
        if (e.code === "EEXIST") {
            console.error ("File already exists.");
            var readline = require('readline');
            var rl = readline.createInterface(process.stdin, process.stdout);
            rl.question("Overwrite? [yes]/no: ", function(answer) {
                    if(answer == "no") {
                        console.log ("Not overwritting, bye.");
                        process.exit (1);
                    }
                    else {
                        console.log ("Overwriting file.");
                        try {
                            fs.writeFileSync(filename, data, {flag: 'w'});
                        } catch (ee) { throw ee; }
                        rl.close();
                    }
                });//question()
        }
        else { throw e; }
    }//catch(e)
}

writeData ("/tmp/foo", "foo bar");
console.log ("do something else");
writeData ("/tmp/bar", "bar baz");
console.log ("done.");
process.exit(0);

This fails: the program does not wait for user input and exists.

NOTE: you need to run it twice to see the failure. the first time, it creates the files fine.

How should I proceed?

oluc
  • 311
  • 1
  • 4
  • 8
  • not yet, I got busy by other things, but I'll come back to the issue for sure! Thanks for your answers – oluc Mar 17 '14 at 17:45

2 Answers2

4

Since writeData is calling some asynchronous functions (readline), it needs provide a call back (which you pass in the call to the function and after file update, you call back. To be clear, writeData is still synchronous since you're calling xxxSync methods (and you're not making it synchronous by adding the callback) but readline is async. So you're function must wait until readline completes. The callback is the signal to continue which is why the next console.writeline is inside the callback.

Otherwise, console.log will run before readline completing inside of writeData:

writeData ("/tmp/foo", "foo bar");
console.log ("do something else");

You need to do something like:

function writeData (filename, data, fn) {
    ...
        rl.question("Overwrite? [yes]/no: ", function(answer) {
            if (answer === 'yes) {
                  // do work
                  fn(null);  // null is err
                  return;

Then in the calls:

writeData ("/tmp/foo", "foo bar", function(err) {
    if (err) { 
        // handle, return, 
    }
    console.log ("do something else");
});

Checkout async.js to help unwind some of this nested callback hell.

bryanmac
  • 38,941
  • 11
  • 91
  • 99
3

Make your writeData asynchronous. That fits better with the node.js paradigm:

var fs = require('fs');
var readline = require('readline');
var rl = readline.createInterface(process.stdin, process.stdout);    

function writeData (filename, data, done){
  fs.writeFile(filename, data, {flag: "wx"}, function (err) {
    if (err){
      if(err.code == "EEXIST"){
        console.error("File " + filename + "already exists.");
        rl.question("Overwrite? [yes]/no: ", function(answer) {
          if(answer === "no") {
              console.log ("Not overwritting " + filename);
          } else {
              console.log ("Overwriting " + filename);
              fs.writeFile(filename, data, done);
          }
        });
      } else {
        if(done) done(err);
      }
    }
    else if(done) done(null);
  });
}

writeData ("/tmp/foo", "foo bar", function(err){
  if(err) throw err;
  writeData ("/tmp/bar", "bar baz", function(){process.exit(0)});
});
Zeta
  • 103,620
  • 13
  • 194
  • 236
  • I have other functions after writing the data, that exits the process on error. However removing the `exit()` does not work better: the prompts are passed, the `console.log()` are printed, and only then it query user input (with no prompt) -- and btw only one answer is taken. Try it! – oluc Mar 14 '14 at 11:09